From: Jo-Philipp Wich Date: Tue, 13 Sep 2022 21:50:12 +0000 (+0200) Subject: treewide: separate Lua runtime resources X-Git-Url: http://git.openwrt.org/%22https:/collectd.org//%22/%22https:/collectd.org/%22?a=commitdiff_plain;h=673f38246ac3548caefec41183e3dd7477d9f6f6;p=project%2Fluci.git treewide: separate Lua runtime resources Move classes required for Lua runtime support into a new `luci-lua-runtime` package. Also replace the `luci.http` and `luci.util` classes in `luci-lib-base` with stubbed versions interacting with the ucode based runtime environment. Finally merge `luci-base-ucode` into the remainders of `luci-base`. Signed-off-by: Jo-Philipp Wich --- diff --git a/.gitignore b/.gitignore index 60dd7e18d6..7834b9bce4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,10 @@ package-lock.json modules/luci-base/src/po2lmo modules/luci-base/src/jsmin modules/luci-base/src/contrib/lemon -modules/luci-base/src/plural_formula.c -modules/luci-base/src/plural_formula.h +modules/luci-base/src/ucode/plural_formula.c +modules/luci-base/src/ucode/plural_formula.h +modules/luci-compat/src/contrib/lemon +modules/luci-compat/src/plural_formula.c +modules/luci-compat/src/plural_formula.h docs/jsapi/* !docs/jsapi/README.md diff --git a/build/mkbasepot.sh b/build/mkbasepot.sh index 0f9247536b..d59a151d6a 100755 --- a/build/mkbasepot.sh +++ b/build/mkbasepot.sh @@ -8,7 +8,7 @@ echo -n "Updating modules/luci-base/po/templates/base.pot ... " ./build/i18n-scan.pl \ - modules/luci-base/ modules/luci-compat/ modules/luci-mod-admin-full/ \ + modules/luci-base/ modules/luci-compat/ modules/luci-lua-runtime/ \ modules/luci-mod-network modules/luci-mod-status modules/luci-mod-system/ \ protocols/ themes/ \ > modules/luci-base/po/templates/base.pot diff --git a/build/zoneinfo2ucode.pl b/build/zoneinfo2ucode.pl index 35dfac3797..941255f2f4 100755 --- a/build/zoneinfo2ucode.pl +++ b/build/zoneinfo2ucode.pl @@ -7,7 +7,7 @@ use strict; my %TZ; my $tzdin = $ARGV[0] || "/usr/share/zoneinfo"; -my $tzdout = $ARGV[1] || "./modules/luci-base-ucode/ucode/zoneinfo.uc"; +my $tzdout = $ARGV[1] || "./modules/luci-base/ucode/zoneinfo.uc"; local $/ = "\012"; open( ZTAB, "< $tzdin/zone.tab" ) || die "open($tzdin/zone.tab): $!"; diff --git a/libs/luci-lib-base/luasrc/http.lua b/libs/luci-lib-base/luasrc/http.lua index 20b55f2854..06547ae2ce 100644 --- a/libs/luci-lib-base/luasrc/http.lua +++ b/libs/luci-lib-base/luasrc/http.lua @@ -6,234 +6,66 @@ local util = require "luci.util" local coroutine = require "coroutine" local table = require "table" local lhttp = require "lucihttp" -local nixio = require "nixio" -local ltn12 = require "luci.ltn12" -local table, ipairs, pairs, type, tostring, tonumber, error = - table, ipairs, pairs, type, tostring, tonumber, error +local L, table, ipairs, pairs, type, error = _G.L, table, ipairs, pairs, type, error module "luci.http" HTTP_MAX_CONTENT = 1024*100 -- 100 kB maximum content size -context = util.threadlocal() - -Request = util.class() -function Request.__init__(self, env, sourcein, sinkerr) - self.input = sourcein - self.error = sinkerr - - - -- File handler nil by default to let .content() work - self.filehandler = nil - - -- HTTP-Message table - self.message = { - env = env, - headers = {}, - params = urldecode_params(env.QUERY_STRING or ""), - } - - self.parsed_input = false -end - -function Request.formvalue(self, name, noparse) - if not noparse and not self.parsed_input then - self:_parse_input() - end - - if name then - return self.message.params[name] - else - return self.message.params - end -end - -function Request.formvaluetable(self, prefix) - local vals = {} - prefix = prefix and prefix .. "." or "." - - if not self.parsed_input then - self:_parse_input() - end - - local void = self.message.params[nil] - for k, v in pairs(self.message.params) do - if k:find(prefix, 1, true) == 1 then - vals[k:sub(#prefix + 1)] = tostring(v) - end - end - - return vals -end - -function Request.content(self) - if not self.parsed_input then - self:_parse_input() - end - - return self.message.content, self.message.content_length -end - -function Request.getcookie(self, name) - return lhttp.header_attribute("cookie; " .. (self:getenv("HTTP_COOKIE") or ""), name) -end - -function Request.getenv(self, name) - if name then - return self.message.env[name] - else - return self.message.env - end -end - -function Request.setfilehandler(self, callback) - self.filehandler = callback - - if not self.parsed_input then - return - end - - -- If input has already been parsed then uploads are stored as unlinked - -- temporary files pointed to by open file handles in the parameter - -- value table. Loop all params, and invoke the file callback for any - -- param with an open file handle. - local name, value - for name, value in pairs(self.message.params) do - if type(value) == "table" then - while value.fd do - local data = value.fd:read(1024) - local eof = (not data or data == "") - - callback(value, data, eof) - - if eof then - value.fd:close() - value.fd = nil - end - end - end - end -end - -function Request._parse_input(self) - parse_message_body( - self.input, - self.message, - self.filehandler - ) - self.parsed_input = true -end - function close() - if not context.eoh then - context.eoh = true - coroutine.yield(3) - end - - if not context.closed then - context.closed = true - coroutine.yield(5) - end + L.http:close() end function content() - return context.request:content() + return L.http:content() end function formvalue(name, noparse) - return context.request:formvalue(name, noparse) + return L.http:formvalue(name, noparse) end function formvaluetable(prefix) - return context.request:formvaluetable(prefix) + return L.http:formvaluetable(prefix) end function getcookie(name) - return context.request:getcookie(name) + return L.http:getcookie(name) end -- or the environment table itself. function getenv(name) - return context.request:getenv(name) + return L.http:getenv(name) end function setfilehandler(callback) - return context.request:setfilehandler(callback) + return L.http:setfilehandler(callback) end function header(key, value) - if not context.headers then - context.headers = {} - end - context.headers[key:lower()] = value - coroutine.yield(2, key, value) + L.http:header(key, value) end function prepare_content(mime) - if not context.headers or not context.headers["content-type"] then - if mime == "application/xhtml+xml" then - if not getenv("HTTP_ACCEPT") or - not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then - mime = "text/html; charset=UTF-8" - end - header("Vary", "Accept") - end - header("Content-Type", mime) - end + L.http:prepare_content(mime) end function source() - return context.request.input + return L.http.input end function status(code, message) - code = code or 200 - message = message or "OK" - context.status = code - coroutine.yield(1, code, message) + L.http:status(code, message) end -- This function is as a valid LTN12 sink. -- If the content chunk is nil this function will automatically invoke close. function write(content, src_err) - if not content then - if src_err then - error(src_err) - else - close() - end - return true - elseif #content == 0 then - return true - else - if not context.eoh then - if not context.status then - status() - end - if not context.headers or not context.headers["content-type"] then - header("Content-Type", "text/html; charset=utf-8") - end - if not context.headers["cache-control"] then - header("Cache-Control", "no-cache") - header("Expires", "0") - end - if not context.headers["x-frame-options"] then - header("X-Frame-Options", "SAMEORIGIN") - end - if not context.headers["x-xss-protection"] then - header("X-XSS-Protection", "1; mode=block") - end - if not context.headers["x-content-type-options"] then - header("X-Content-Type-Options", "nosniff") - end - - context.eoh = true - coroutine.yield(3) - end - coroutine.yield(4, content) - return true + if src_err then + error(src_err) end + + return L.print(content) end function splice(fd, size) @@ -241,10 +73,7 @@ function splice(fd, size) end function redirect(url) - if url == "" then url = "/" end - status(302, "Found") - header("Location", url) - close() + L.http:redirect(url) end function build_querystring(q) @@ -266,35 +95,7 @@ urldecode = util.urldecode urlencode = util.urlencode function write_json(x) - util.serialize_json(x, write) -end - --- from given url or string. Returns a table with urldecoded values. --- Simple parameters are stored as string values associated with the parameter --- name within the table. Parameters with multiple values are stored as array --- containing the corresponding values. -function urldecode_params(url, tbl) - local parser, name - local params = tbl or { } - - parser = lhttp.urlencoded_parser(function (what, buffer, length) - if what == parser.TUPLE then - name, value = nil, nil - elseif what == parser.NAME then - name = lhttp.urldecode(buffer) - elseif what == parser.VALUE and name then - params[name] = lhttp.urldecode(buffer) or "" - end - - return true - end) - - if parser then - parser:parse((url or ""):match("[^?]*$")) - parser:parse(nil) - end - - return params + L.printf('%J', x) end -- separated by "&". Tables are encoded as parameters with multiple values by @@ -332,223 +133,12 @@ function urlencode_params(tbl) return table.concat(enc, "") end --- Content-Type. Stores all extracted data associated with its parameter name --- in the params table within the given message object. Multiple parameter --- values are stored as tables, ordinary ones as strings. --- If an optional file callback function is given then it is fed with the --- file contents chunk by chunk and only the extracted file name is stored --- within the params table. The callback function will be called subsequently --- with three arguments: --- o Table containing decoded (name, file) and raw (headers) mime header data --- o String value containing a chunk of the file data --- o Boolean which indicates whether the current chunk is the last one (eof) -function mimedecode_message_body(src, msg, file_cb) - local parser, header, field - local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil) - - parser, err = lhttp.multipart_parser(msg.env.CONTENT_TYPE, function (what, buffer, length) - if what == parser.PART_INIT then - field = { } - - elseif what == parser.HEADER_NAME then - header = buffer:lower() - - elseif what == parser.HEADER_VALUE and header then - if header:lower() == "content-disposition" and - lhttp.header_attribute(buffer, nil) == "form-data" - then - field.name = lhttp.header_attribute(buffer, "name") - field.file = lhttp.header_attribute(buffer, "filename") - field[1] = field.file - end - - if field.headers then - field.headers[header] = buffer - else - field.headers = { [header] = buffer } - end - - elseif what == parser.PART_BEGIN then - return not field.file - - elseif what == parser.PART_DATA and field.name and length > 0 then - if field.file then - if file_cb then - file_cb(field, buffer, false) - msg.params[field.name] = msg.params[field.name] or field - else - if not field.fd then - field.fd = nixio.mkstemp(field.name) - end - - if field.fd then - field.fd:write(buffer) - msg.params[field.name] = msg.params[field.name] or field - end - end - else - field.value = buffer - end - - elseif what == parser.PART_END and field.name then - if field.file and msg.params[field.name] then - if file_cb then - file_cb(field, "", true) - elseif field.fd then - field.fd:seek(0, "set") - end - else - local val = msg.params[field.name] - - if type(val) == "table" then - val[#val+1] = field.value or "" - elseif val ~= nil then - msg.params[field.name] = { val, field.value or "" } - else - msg.params[field.name] = field.value or "" - end - end - - field = nil - - elseif what == parser.ERROR then - err = buffer - end - - return true - end, HTTP_MAX_CONTENT) - - return ltn12.pump.all(src, function (chunk) - len = len + (chunk and #chunk or 0) - - if maxlen and len > maxlen + 2 then - return nil, "Message body size exceeds Content-Length" - end - - if not parser or not parser:parse(chunk) then - return nil, err - end - - return true - end) -end - --- Content-Type. Stores all extracted data associated with its parameter name --- in the params table within the given message object. Multiple parameter --- values are stored as tables, ordinary ones as strings. -function urldecode_message_body(src, msg) - local err, name, value, parser - local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil) - - parser = lhttp.urlencoded_parser(function (what, buffer, length) - if what == parser.TUPLE then - name, value = nil, nil - elseif what == parser.NAME then - name = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) - elseif what == parser.VALUE and name then - local val = msg.params[name] - - if type(val) == "table" then - val[#val+1] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" - elseif val ~= nil then - msg.params[name] = { val, lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" } - else - msg.params[name] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" - end - elseif what == parser.ERROR then - err = buffer - end - - return true - end, HTTP_MAX_CONTENT) - - return ltn12.pump.all(src, function (chunk) - len = len + (chunk and #chunk or 0) - - if maxlen and len > maxlen + 2 then - return nil, "Message body size exceeds Content-Length" - elseif len > HTTP_MAX_CONTENT then - return nil, "Message body size exceeds maximum allowed length" - end - - if not parser or not parser:parse(chunk) then - return nil, err - end - - return true - end) -end - --- This function will examine the Content-Type within the given message object --- to select the appropriate content decoder. --- Currently the application/x-www-urlencoded and application/form-data --- mime types are supported. If the encountered content encoding can't be --- handled then the whole message body will be stored unaltered as "content" --- property within the given message object. -function parse_message_body(src, msg, filecb) - if msg.env.CONTENT_LENGTH or msg.env.REQUEST_METHOD == "POST" then - local ctype = lhttp.header_attribute(msg.env.CONTENT_TYPE, nil) - - -- Is it multipart/mime ? - if ctype == "multipart/form-data" then - return mimedecode_message_body(src, msg, filecb) - - -- Is it application/x-www-form-urlencoded ? - elseif ctype == "application/x-www-form-urlencoded" then - return urldecode_message_body(src, msg) - - end - - -- Unhandled encoding - -- If a file callback is given then feed it chunk by chunk, else - -- store whole buffer in message.content - local sink - - -- If we have a file callback then feed it - if type(filecb) == "function" then - local meta = { - name = "raw", - encoding = msg.env.CONTENT_TYPE - } - sink = function( chunk ) - if chunk then - return filecb(meta, chunk, false) - else - return filecb(meta, nil, true) - end - end - -- ... else append to .content - else - msg.content = "" - msg.content_length = 0 - - sink = function( chunk ) - if chunk then - if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then - msg.content = msg.content .. chunk - msg.content_length = msg.content_length + #chunk - return true - else - return nil, "POST data exceeds maximum allowed length" - end - end - return true - end - end - - -- Pump data... - while true do - local ok, err = ltn12.pump.step( src, sink ) - - if not ok and err then - return nil, err - elseif not ok then -- eof - return true - end - end - - return true - end - - return false -end +context = { + request = { + formvalue = function(self, ...) return formvalue(...) end; + formvaluetable = function(self, ...) return formvaluetable(...) end; + content = function(self, ...) return content(...) end; + getcookie = function(self, ...) return getcookie(...) end; + setfilehandler = function(self, ...) return setfilehandler(...) end; + } +} diff --git a/libs/luci-lib-base/luasrc/util.lua b/libs/luci-lib-base/luasrc/util.lua index 89757917ff..80013179aa 100644 --- a/libs/luci-lib-base/luasrc/util.lua +++ b/libs/luci-lib-base/luasrc/util.lua @@ -100,32 +100,8 @@ end -- Scope manipulation routines -- -coxpt = setmetatable({}, { __mode = "kv" }) - -local tl_meta = { - __mode = "k", - - __index = function(self, key) - local t = rawget(self, coxpt[coroutine.running()] - or coroutine.running() or 0) - return t and t[key] - end, - - __newindex = function(self, key, value) - local c = coxpt[coroutine.running()] or coroutine.running() or 0 - local r = rawget(self, c) - if not r then - rawset(self, c, { [key] = value }) - else - r[key] = value - end - end -} - --- the current active coroutine. A thread local store is private a table object --- whose values can't be accessed from outside of the running coroutine. function threadlocal(tbl) - return setmetatable(tbl or {}, tl_meta) + return tbl or {} end @@ -772,7 +748,6 @@ function coxpcall(f, err, ...) co = coroutine.create(newf) end coromap[co] = current - coxpt[co] = coxpt[current] or current or 0 return performResume(err, co, ...) end end diff --git a/modules/luci-base-ucode/Makefile b/modules/luci-base-ucode/Makefile deleted file mode 100644 index 2d5eb84d94..0000000000 --- a/modules/luci-base-ucode/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -# -# Copyright (C) 2022 Jo-Philipp Wich -# -# This is free software, licensed under the Apache License, Version 2.0 . -# - -include $(TOPDIR)/rules.mk - -PKG_NAME:=luci-base-ucode - -LUCI_TYPE:=mod -LUCI_BASENAME:=base-ucode - -LUCI_TITLE:=LuCI core ucode runtime -LUCI_DEPENDS:=\ - +luci-base \ - +ucode \ - +ucode-mod-fs \ - +ucode-mod-uci \ - +ucode-mod-ubus \ - +ucode-mod-math \ - +ucode-mod-lua \ - +ucode-mod-html \ - +rpcd-mod-ucode \ - +liblucihttp-ucode - -PKG_LICENSE:=MIT - -define Package/luci-base-ucode/postinst -#!/bin/sh - -if [ -z "$${PKG_INSTROOT}" ] && [ -f /etc/config/uhttpd ]; then - if ! uci -q get uhttpd.main.ucode_prefix | grep -sq /cgi-bin/luci-ucode; then - uci add_list uhttpd.main.ucode_prefix='/cgi-bin/luci-ucode=/usr/share/ucode/luci/uhttpd.uc' - uci commit uhttpd - service uhttpd reload - fi -fi - -exit 0 -endef - -include ../../luci.mk - -# call BuildPackage - OpenWrt buildroot signature diff --git a/modules/luci-base-ucode/htdocs/cgi-bin/luci-ucode b/modules/luci-base-ucode/htdocs/cgi-bin/luci-ucode deleted file mode 100755 index 442e427d41..0000000000 --- a/modules/luci-base-ucode/htdocs/cgi-bin/luci-ucode +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env ucode - -'use strict'; - -import { stdin, stdout } from 'fs'; - -import dispatch from 'luci.dispatcher'; -import request from 'luci.http'; - -const input_bufsize = 4096; -let input_available = +getenv('CONTENT_LENGTH') || 0; - -function read(len) { - if (input_available == 0) { - stdin.close(); - - return null; - } - - let chunk = stdin.read(min(input_available, len ?? input_bufsize, input_bufsize)); - - if (chunk == null) { - input_available = 0; - stdin.close(); - } - else { - input_available -= length(chunk); - } - - return chunk; -} - -function write(data) { - return stdout.write(data); -} - -let req = request(getenv(), read, write); - -dispatch(req); - -req.close(); diff --git a/modules/luci-base-ucode/luasrc/ucodebridge/luci/dispatcher.lua b/modules/luci-base-ucode/luasrc/ucodebridge/luci/dispatcher.lua deleted file mode 100644 index 8889853b98..0000000000 --- a/modules/luci-base-ucode/luasrc/ucodebridge/luci/dispatcher.lua +++ /dev/null @@ -1,457 +0,0 @@ --- Copyright 2008 Steven Barth --- Copyright 2008-2015 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -module("luci.dispatcher", package.seeall) - -local http = _G.L.http - -context = setmetatable({ - request = _G.L.ctx.request_path; - requested = _G.L.node; - dispatched = _G.L.node; -}, { - __index = function(t, k) - if k == "requestpath" then - return _G.L.ctx.request_path - elseif k == "requestargs" then - return _G.L.ctx.request_args - else - return _G.L.ctx[k] - end - end -}) - -uci = require "luci.model.uci" -uci:set_session_id(_G.L.ctx.authsession) - -i18n = require "luci.i18n" -i18n.setlanguage(_G.L.dispatcher.lang) - -build_url = _G.L.dispatcher.build_url -menu_json = _G.L.dispatcher.menu_json -error404 = _G.L.dispatcher.error404 -error500 = _G.L.dispatcher.error500 - -function is_authenticated(auth) - local session = _G.L.dispatcher.is_authenticated(auth) - if session then - return session.sid, session.data, session.acls - end -end - -function assign(path, clone, title, order) - local obj = node(unpack(path)) - - obj.title = title - obj.order = order - - setmetatable(obj, {__index = node(unpack(clone))}) - - return obj -end - -function entry(path, target, title, order) - local c = node(unpack(path)) - - c.title = title - c.order = order - c.action = target - - return c -end - --- enabling the node. -function get(...) - return node(...) -end - -function node(...) - local p = table.concat({ ... }, "/") - - if not __entries[p] then - __entries[p] = {} - end - - return __entries[p] -end - -function lookup(...) - local i, path = nil, {} - for i = 1, select('#', ...) do - local name, arg = nil, tostring(select(i, ...)) - for name in arg:gmatch("[^/]+") do - path[#path+1] = name - end - end - - local node = menu_json() - for i = 1, #path do - node = node.children[path[i]] - - if not node then - return nil - elseif node.leaf then - break - end - end - - return node, build_url(unpack(path)) -end - - -function process_lua_controller(path) - local base = "/usr/lib/lua/luci/controller/" - local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".") - local mod = require(modname) - assert(mod ~= true, - "Invalid controller file found\n" .. - "The file '" .. path .. "' contains an invalid module line.\n" .. - "Please verify whether the module name is set to '" .. modname .. - "' - It must correspond to the file path!") - - local idx = mod.index - if type(idx) ~= "function" then - return nil - end - - local entries = {} - - __entries = entries - __controller = modname - - setfenv(idx, setmetatable({}, { __index = luci.dispatcher }))() - - __entries = nil - __controller = nil - - -- fixup gathered node specs - for path, entry in pairs(entries) do - if entry.leaf then - entry.wildcard = true - end - - if type(entry.file_depends) == "table" then - for _, v in ipairs(entry.file_depends) do - entry.depends = entry.depends or {} - entry.depends.fs = entry.depends.fs or {} - - local ft = fs.stat(v, "type") - if ft == "dir" then - entry.depends.fs[v] = "directory" - elseif v:match("/s?bin/") then - entry.depends.fs[v] = "executable" - else - entry.depends.fs[v] = "file" - end - end - end - - if type(entry.uci_depends) == "table" then - for k, v in pairs(entry.uci_depends) do - entry.depends = entry.depends or {} - entry.depends.uci = entry.depends.uci or {} - entry.depends.uci[k] = v - end - end - - if type(entry.acl_depends) == "table" then - for _, acl in ipairs(entry.acl_depends) do - entry.depends = entry.depends or {} - entry.depends.acl = entry.depends.acl or {} - entry.depends.acl[#entry.depends.acl + 1] = acl - end - end - - if (entry.sysauth_authenticator ~= nil) or - (entry.sysauth ~= nil and entry.sysauth ~= false) - then - if entry.sysauth_authenticator == "htmlauth" then - entry.auth = { - login = true, - methods = { "cookie:sysauth_https", "cookie:sysauth_http" } - } - elseif subname == "rpc" and entry.module == "luci.controller.rpc" then - entry.auth = { - login = false, - methods = { "query:auth", "cookie:sysauth_https", "cookie:sysauth_http" } - } - elseif entry.module == "luci.controller.admin.uci" then - entry.auth = { - login = false, - methods = { "param:sid" } - } - end - elseif entry.sysauth == false then - entry.auth = {} - end - - entry.leaf = nil - - entry.file_depends = nil - entry.uci_depends = nil - entry.acl_depends = nil - - entry.sysauth = nil - entry.sysauth_authenticator = nil - end - - return entries -end - -function invoke_cbi_action(model, config, ...) - local cbi = require "luci.cbi" - local tpl = require "luci.template" - local util = require "luci.util" - - if not config then - config = {} - end - - local maps = cbi.load(model, ...) - - local state = nil - - local function has_uci_access(config, level) - local rv = util.ubus("session", "access", { - ubus_rpc_session = context.authsession, - scope = "uci", object = config, - ["function"] = level - }) - - return (type(rv) == "table" and rv.access == true) or false - end - - local i, res - for i, res in ipairs(maps) do - if util.instanceof(res, cbi.SimpleForm) then - io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n" - % model) - - io.stderr:write("please change %s to use the form() action instead.\n" - % table.concat(context.request, "/")) - end - - res.flow = config - local cstate = res:parse() - if cstate and (not state or cstate < state) then - state = cstate - end - end - - local function _resolve_path(path) - return type(path) == "table" and build_url(unpack(path)) or path - end - - if config.on_valid_to and state and state > 0 and state < 2 then - http:redirect(_resolve_path(config.on_valid_to)) - return - end - - if config.on_changed_to and state and state > 1 then - http:redirect(_resolve_path(config.on_changed_to)) - return - end - - if config.on_success_to and state and state > 0 then - http:redirect(_resolve_path(config.on_success_to)) - return - end - - if config.state_handler then - if not config.state_handler(state, maps) then - return - end - end - - http:header("X-CBI-State", state or 0) - - if not config.noheader then - tpl.render("cbi/header", {state = state}) - end - - local redirect - local messages - local applymap = false - local pageaction = true - local parsechain = { } - local writable = false - - for i, res in ipairs(maps) do - if res.apply_needed and res.parsechain then - local c - for _, c in ipairs(res.parsechain) do - parsechain[#parsechain+1] = c - end - applymap = true - end - - if res.redirect then - redirect = redirect or res.redirect - end - - if res.pageaction == false then - pageaction = false - end - - if res.message then - messages = messages or { } - messages[#messages+1] = res.message - end - end - - for i, res in ipairs(maps) do - local is_readable_map = has_uci_access(res.config, "read") - local is_writable_map = has_uci_access(res.config, "write") - - writable = writable or is_writable_map - - res:render({ - firstmap = (i == 1), - redirect = redirect, - messages = messages, - pageaction = pageaction, - parsechain = parsechain, - readable = is_readable_map, - writable = is_writable_map - }) - end - - if not config.nofooter then - tpl.render("cbi/footer", { - flow = config, - pageaction = pageaction, - redirect = redirect, - state = state, - autoapply = config.autoapply, - trigger_apply = applymap, - writable = writable - }) - end -end - -function invoke_form_action(model, ...) - local cbi = require "luci.cbi" - local tpl = require "luci.template" - - local maps = luci.cbi.load(model, ...) - local state = nil - - local i, res - for i, res in ipairs(maps) do - local cstate = res:parse() - if cstate and (not state or cstate < state) then - state = cstate - end - end - - http:header("X-CBI-State", state or 0) - tpl.render("header") - for i, res in ipairs(maps) do - res:render() - end - tpl.render("footer") -end - - -function call(name, ...) - return { - ["type"] = "call", - ["module"] = __controller, - ["function"] = name, - ["parameters"] = select('#', ...) > 0 and {...} or nil - } -end - -function post(name, ...) - return { - ["type"] = "call", - ["module"] = __controller, - ["function"] = name, - ["parameters"] = select('#', ...) > 0 and {...} or nil, - ["post"] = true - } -end - -function view(name) - return { - ["type"] = "view", - ["path"] = name - } -end - -function template(name) - return { - ["type"] = "template", - ["path"] = name - } -end - -function cbi(model, config) - return { - ["type"] = "call", - ["module"] = "luci.dispatcher", - ["function"] = "invoke_cbi_action", - ["parameters"] = { model, config }, - ["post"] = { - ["cbi.submit"] = true - } - } -end - -function form(model) - return { - ["type"] = "call", - ["module"] = "luci.dispatcher", - ["function"] = "invoke_form_action", - ["parameters"] = { model }, - ["post"] = { - ["cbi.submit"] = true - } - } -end - -function firstchild() - return { - ["type"] = "firstchild" - } -end - -function firstnode() - return { - ["type"] = "firstchild", - ["recurse"] = true - } -end - -function arcombine(trg1, trg2) - return { - ["type"] = "arcombine", - ["targets"] = { trg1, trg2 } --, - --env = getfenv(), - } -end - -function alias(...) - return { - ["type"] = "alias", - ["path"] = table.concat({ ... }, "/") - } -end - -function rewrite(n, ...) - return { - ["type"] = "rewrite", - ["path"] = table.concat({ ... }, "/"), - ["remove"] = n - } -end - - -translate = i18n.translate - --- This function does not actually translate the given argument but --- is used by build/i18n-scan.pl to find translatable entries. -function _(text) - return text -end diff --git a/modules/luci-base-ucode/luasrc/ucodebridge/luci/http.lua b/modules/luci-base-ucode/luasrc/ucodebridge/luci/http.lua deleted file mode 100644 index 06547ae2ce..0000000000 --- a/modules/luci-base-ucode/luasrc/ucodebridge/luci/http.lua +++ /dev/null @@ -1,144 +0,0 @@ --- Copyright 2008 Steven Barth --- Copyright 2010-2018 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local util = require "luci.util" -local coroutine = require "coroutine" -local table = require "table" -local lhttp = require "lucihttp" - -local L, table, ipairs, pairs, type, error = _G.L, table, ipairs, pairs, type, error - -module "luci.http" - -HTTP_MAX_CONTENT = 1024*100 -- 100 kB maximum content size - -function close() - L.http:close() -end - -function content() - return L.http:content() -end - -function formvalue(name, noparse) - return L.http:formvalue(name, noparse) -end - -function formvaluetable(prefix) - return L.http:formvaluetable(prefix) -end - -function getcookie(name) - return L.http:getcookie(name) -end - --- or the environment table itself. -function getenv(name) - return L.http:getenv(name) -end - -function setfilehandler(callback) - return L.http:setfilehandler(callback) -end - -function header(key, value) - L.http:header(key, value) -end - -function prepare_content(mime) - L.http:prepare_content(mime) -end - -function source() - return L.http.input -end - -function status(code, message) - L.http:status(code, message) -end - --- This function is as a valid LTN12 sink. --- If the content chunk is nil this function will automatically invoke close. -function write(content, src_err) - if src_err then - error(src_err) - end - - return L.print(content) -end - -function splice(fd, size) - coroutine.yield(6, fd, size) -end - -function redirect(url) - L.http:redirect(url) -end - -function build_querystring(q) - local s, n, k, v = {}, 1, nil, nil - - for k, v in pairs(q) do - s[n+0] = (n == 1) and "?" or "&" - s[n+1] = util.urlencode(k) - s[n+2] = "=" - s[n+3] = util.urlencode(v) - n = n + 4 - end - - return table.concat(s, "") -end - -urldecode = util.urldecode - -urlencode = util.urlencode - -function write_json(x) - L.printf('%J', x) -end - --- separated by "&". Tables are encoded as parameters with multiple values by --- repeating the parameter name with each value. -function urlencode_params(tbl) - local k, v - local n, enc = 1, {} - for k, v in pairs(tbl) do - if type(v) == "table" then - local i, v2 - for i, v2 in ipairs(v) do - if enc[1] then - enc[n] = "&" - n = n + 1 - end - - enc[n+0] = lhttp.urlencode(k) - enc[n+1] = "=" - enc[n+2] = lhttp.urlencode(v2) - n = n + 3 - end - else - if enc[1] then - enc[n] = "&" - n = n + 1 - end - - enc[n+0] = lhttp.urlencode(k) - enc[n+1] = "=" - enc[n+2] = lhttp.urlencode(v) - n = n + 3 - end - end - - return table.concat(enc, "") -end - -context = { - request = { - formvalue = function(self, ...) return formvalue(...) end; - formvaluetable = function(self, ...) return formvaluetable(...) end; - content = function(self, ...) return content(...) end; - getcookie = function(self, ...) return getcookie(...) end; - setfilehandler = function(self, ...) return setfilehandler(...) end; - } -} diff --git a/modules/luci-base-ucode/luasrc/ucodebridge/luci/template.lua b/modules/luci-base-ucode/luasrc/ucodebridge/luci/template.lua deleted file mode 100644 index b7cc56e1cc..0000000000 --- a/modules/luci-base-ucode/luasrc/ucodebridge/luci/template.lua +++ /dev/null @@ -1,184 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -local util = require "luci.util" -local config = require "luci.config" -local tparser = require "luci.template.parser" - -local tostring, pairs, loadstring = tostring, pairs, loadstring -local setmetatable, loadfile = setmetatable, loadfile -local getfenv, setfenv, rawget = getfenv, setfenv, rawget -local assert, type, error = assert, type, error -local table, string, unpack = table, string, unpack - - ---- ---- bootstrap ---- -local _G = _G -local L = _G.L - -local http = _G.L.http - -local disp = require "luci.dispatcher" -local i18n = require "luci.i18n" -local xml = require "luci.xml" -local fs = require "nixio.fs" - - ---- LuCI template library. -module "luci.template" - -config.template = config.template or {} -viewdir = config.template.viewdir or util.libpath() .. "/view" - - --- Define the namespace for template modules -context = {} --util.threadlocal() - ---- Render a certain template. --- @param name Template name --- @param scope Scope to assign to template (optional) -function render(name, scope) - return Template(name):render(scope or getfenv(2)) -end - ---- Render a template from a string. --- @param template Template string --- @param scope Scope to assign to template (optional) -function render_string(template, scope) - return Template(nil, template):render(scope or getfenv(2)) -end - - --- Template class -Template = util.class() - --- Shared template cache to store templates in to avoid unnecessary reloading -Template.cache = setmetatable({}, {__mode = "v"}) - - - -local function _ifattr(cond, key, val, noescape) - if cond then - local env = getfenv(3) - local scope = (type(env.self) == "table") and env.self - if type(val) == "table" then - if not next(val) then - return '' - else - val = util.serialize_json(val) - end - end - - val = tostring(val or - (type(env[key]) ~= "function" and env[key]) or - (scope and type(scope[key]) ~= "function" and scope[key]) or "") - - if noescape ~= true then - val = xml.pcdata(val) - end - - return string.format(' %s="%s"', tostring(key), val) - else - return '' - end -end - -context.viewns = setmetatable({ - include = function(name) - if fs.access(viewdir .. "/" .. name .. ".htm") then - Template(name):render(getfenv(2)) - else - L.include(name, getfenv(2)) - end - end; - translate = i18n.translate; - translatef = i18n.translatef; - export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end; - striptags = xml.striptags; - pcdata = xml.pcdata; - ifattr = function(...) return _ifattr(...) end; - attr = function(...) return _ifattr(true, ...) end; - url = disp.build_url; -}, {__index=function(tbl, key) - if key == "controller" then - return disp.build_url() - elseif key == "REQUEST_URI" then - return disp.build_url(unpack(disp.context.requestpath)) - elseif key == "FULL_REQUEST_URI" then - local url = { http:getenv("SCRIPT_NAME") or "", http:getenv("PATH_INFO") } - local query = http:getenv("QUERY_STRING") - if query and #query > 0 then - url[#url+1] = "?" - url[#url+1] = query - end - return table.concat(url, "") - elseif key == "token" then - return disp.context.authtoken - elseif key == "theme" then - return L.media and fs.basename(L.media) or tostring(L) - elseif key == "resource" then - return L.config.main.resourcebase - else - return rawget(tbl, key) or _G[key] or L[key] - end -end}) - - --- Constructor - Reads and compiles the template on-demand -function Template.__init__(self, name, template) - if name then - self.template = self.cache[name] - self.name = name - else - self.name = "[string]" - end - - -- Create a new namespace for this template - self.viewns = context.viewns - - -- If we have a cached template, skip compiling and loading - if not self.template then - - -- Compile template - local err - local sourcefile - - if name then - sourcefile = viewdir .. "/" .. name .. ".htm" - self.template, _, err = tparser.parse(sourcefile) - else - sourcefile = "[string]" - self.template, _, err = tparser.parse_string(template) - end - - -- If we have no valid template throw error, otherwise cache the template - if not self.template then - error("Failed to load template '" .. self.name .. "'.\n" .. - "Error while parsing template '" .. sourcefile .. "':\n" .. - (err or "Unknown syntax error")) - elseif name then - self.cache[name] = self.template - end - end -end - - --- Renders a template -function Template.render(self, scope) - scope = scope or getfenv(2) - - -- Put our predefined objects in the scope of the template - setfenv(self.template, setmetatable({}, {__index = - function(tbl, key) - return rawget(tbl, key) or self.viewns[key] or scope[key] - end})) - - -- Now finally render the thing - local stat, err = util.copcall(self.template) - if not stat then - error("Failed to execute template '" .. self.name .. "'.\n" .. - "A runtime error occurred: " .. tostring(err or "(nil)")) - end -end diff --git a/modules/luci-base-ucode/luasrc/ucodebridge/luci/ucodebridge.lua b/modules/luci-base-ucode/luasrc/ucodebridge/luci/ucodebridge.lua deleted file mode 100644 index fa4943dc99..0000000000 --- a/modules/luci-base-ucode/luasrc/ucodebridge/luci/ucodebridge.lua +++ /dev/null @@ -1,52 +0,0 @@ --- Copyright 2022 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local coroutine, assert, error, type, require = coroutine, assert, error, type, require -local tmpl = require "luci.template" -local util = require "luci.util" -local http = require "luci.http" - - ---- LuCI ucode bridge library. -module "luci.ucodebridge" - -local function run(fn, ...) - local co = coroutine.create(fn) - local ok, ret - - while coroutine.status(co) ~= "dead" do - ok, ret = coroutine.resume(co, ...) - - if not ok then - error(ret) - end - end - - return ret -end - -function compile(path) - run(function(path) - return tmpl.Template(path) - end, path) -end - -function render(path, scope) - run(tmpl.render, path, scope) -end - -function call(modname, method, ...) - return run(function(module, method, ...) - local mod = require(modname) - local func = mod[method] - - assert(func ~= nil, - 'Cannot resolve function "' .. method .. '". Is it misspelled or local?') - - assert(type(func) == "function", - 'The symbol "' .. method .. '" does not refer to a function but data ' .. - 'of type "' .. type(func) .. '".') - - return func(...) - end, modname, method, ...) -end diff --git a/modules/luci-base-ucode/luasrc/ucodebridge/luci/util.lua b/modules/luci-base-ucode/luasrc/ucodebridge/luci/util.lua deleted file mode 100644 index d50eb1dd9c..0000000000 --- a/modules/luci-base-ucode/luasrc/ucodebridge/luci/util.lua +++ /dev/null @@ -1,786 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -local io = require "io" -local math = require "math" -local table = require "table" -local debug = require "debug" -local ldebug = require "luci.debug" -local string = require "string" -local coroutine = require "coroutine" -local tparser = require "luci.template.parser" -local json = require "luci.jsonc" -local lhttp = require "lucihttp" - -local _ubus = require "ubus" -local _ubus_connection = nil - -local getmetatable, setmetatable = getmetatable, setmetatable -local rawget, rawset, unpack, select = rawget, rawset, unpack, select -local tostring, type, assert, error = tostring, type, assert, error -local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring -local require, pcall, xpcall = require, pcall, xpcall -local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit - -local L = _G.L - -module "luci.util" - --- --- Pythonic string formatting extension --- -getmetatable("").__mod = function(a, b) - local ok, res - - if not b then - return a - elseif type(b) == "table" then - local k, _ - for k, _ in pairs(b) do if type(b[k]) == "userdata" then b[k] = tostring(b[k]) end end - - ok, res = pcall(a.format, a, unpack(b)) - if not ok then - error(res, 2) - end - return res - else - if type(b) == "userdata" then b = tostring(b) end - - ok, res = pcall(a.format, a, b) - if not ok then - error(res, 2) - end - return res - end -end - - --- --- Class helper routines --- - --- Instantiates a class -local function _instantiate(class, ...) - local inst = setmetatable({}, {__index = class}) - - if inst.__init__ then - inst:__init__(...) - end - - return inst -end - --- The class object can be instantiated by calling itself. --- Any class functions or shared parameters can be attached to this object. --- Attaching a table to the class object makes this table shared between --- all instances of this class. For object parameters use the __init__ function. --- Classes can inherit member functions and values from a base class. --- Class can be instantiated by calling them. All parameters will be passed --- to the __init__ function of this class - if such a function exists. --- The __init__ function must be used to set any object parameters that are not shared --- with other objects of this class. Any return values will be ignored. -function class(base) - return setmetatable({}, { - __call = _instantiate, - __index = base - }) -end - -function instanceof(object, class) - local meta = getmetatable(object) - while meta and meta.__index do - if meta.__index == class then - return true - end - meta = getmetatable(meta.__index) - end - return false -end - - --- --- Scope manipulation routines --- - -coxpt = setmetatable({}, { __mode = "kv" }) - -local tl_meta = { - __mode = "k", - - __index = function(self, key) - local t = rawget(self, coxpt[coroutine.running()] - or coroutine.running() or 0) - L.http:write("\n" %{ tostring(self), tostring(coxpt[coroutine.running()] or coroutine.running() or 0), key, tostring(t and t[key]) }) - return t and t[key] - end, - - __newindex = function(self, key, value) - L.http:write("\n" %{ tostring(self), tostring(coxpt[coroutine.running()] or coroutine.running() or 0), key, tostring(value) }) - local c = coxpt[coroutine.running()] or coroutine.running() or 0 - local r = rawget(self, c) - if not r then - rawset(self, c, { [key] = value }) - else - r[key] = value - end - end -} - --- the current active coroutine. A thread local store is private a table object --- whose values can't be accessed from outside of the running coroutine. -function threadlocal(tbl) - return tbl or {} --setmetatable(tbl or {}, tl_meta) -end - - --- --- Debugging routines --- - -function perror(obj) - return io.stderr:write(tostring(obj) .. "\n") -end - -function dumptable(t, maxdepth, i, seen) - i = i or 0 - seen = seen or setmetatable({}, {__mode="k"}) - - for k,v in pairs(t) do - perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v)) - if type(v) == "table" and (not maxdepth or i < maxdepth) then - if not seen[v] then - seen[v] = true - dumptable(v, maxdepth, i+1, seen) - else - perror(string.rep("\t", i) .. "*** RECURSION ***") - end - end - end -end - - --- --- String and data manipulation routines --- - --- compatibility wrapper for xml.pcdata -function pcdata(value) - local xml = require "luci.xml" - - perror("luci.util.pcdata() has been replaced by luci.xml.pcdata() - Please update your code.") - return xml.pcdata(value) -end - -function urlencode(value) - if value ~= nil then - local str = tostring(value) - return lhttp.urlencode(str, lhttp.ENCODE_IF_NEEDED + lhttp.ENCODE_FULL) - or str - end - return nil -end - -function urldecode(value, decode_plus) - if value ~= nil then - local flag = decode_plus and lhttp.DECODE_PLUS or 0 - local str = tostring(value) - return lhttp.urldecode(str, lhttp.DECODE_IF_NEEDED + flag) - or str - end - return nil -end - --- compatibility wrapper for xml.striptags -function striptags(value) - local xml = require "luci.xml" - - perror("luci.util.striptags() has been replaced by luci.xml.striptags() - Please update your code.") - return xml.striptags(value) -end - -function shellquote(value) - return string.format("'%s'", string.gsub(value or "", "'", "'\\''")) -end - --- for bash, ash and similar shells single-quoted strings are taken --- literally except for single quotes (which terminate the string) --- (and the exception noted below for dash (-) at the start of a --- command line parameter). -function shellsqescape(value) - local res - res, _ = string.gsub(value, "'", "'\\''") - return res -end - --- bash, ash and other similar shells interpret a dash (-) at the start --- of a command-line parameters as an option indicator regardless of --- whether it is inside a single-quoted string. It must be backlash --- escaped to resolve this. This requires in some funky special-case --- handling. It may actually be a property of the getopt function --- rather than the shell proper. -function shellstartsqescape(value) - res, _ = string.gsub(value, "^%-", "\\-") - return shellsqescape(res) -end - --- containing the resulting substrings. The optional max parameter specifies --- the number of bytes to process, regardless of the actual length of the given --- string. The optional last parameter, regex, specifies whether the separator --- sequence is interpreted as regular expression. --- pattern as regular expression (optional, default is false) -function split(str, pat, max, regex) - pat = pat or "\n" - max = max or #str - - local t = {} - local c = 1 - - if #str == 0 then - return {""} - end - - if #pat == 0 then - return nil - end - - if max == 0 then - return str - end - - repeat - local s, e = str:find(pat, c, not regex) - max = max - 1 - if s and max < 0 then - t[#t+1] = str:sub(c) - else - t[#t+1] = str:sub(c, s and s - 1) - end - c = e and e + 1 or #str + 1 - until not s or max < 0 - - return t -end - -function trim(str) - return (str:gsub("^%s*(.-)%s*$", "%1")) -end - -function cmatch(str, pat) - local count = 0 - for _ in str:gmatch(pat) do count = count + 1 end - return count -end - --- one token per invocation, the tokens are separated by whitespace. If the --- input value is a table, it is transformed into a string first. A nil value --- will result in a valid iterator which aborts with the first invocation. -function imatch(v) - if type(v) == "table" then - local k = nil - return function() - k = next(v, k) - return v[k] - end - - elseif type(v) == "number" or type(v) == "boolean" then - local x = true - return function() - if x then - x = false - return tostring(v) - end - end - - elseif type(v) == "userdata" or type(v) == "string" then - return tostring(v):gmatch("%S+") - end - - return function() end -end - --- value or 0 if the unit is unknown. Upper- or lower case is irrelevant. --- Recognized units are: --- o "y" - one year (60*60*24*366) --- o "m" - one month (60*60*24*31) --- o "w" - one week (60*60*24*7) --- o "d" - one day (60*60*24) --- o "h" - one hour (60*60) --- o "min" - one minute (60) --- o "kb" - one kilobyte (1024) --- o "mb" - one megabyte (1024*1024) --- o "gb" - one gigabyte (1024*1024*1024) --- o "kib" - one si kilobyte (1000) --- o "mib" - one si megabyte (1000*1000) --- o "gib" - one si gigabyte (1000*1000*1000) -function parse_units(ustr) - - local val = 0 - - -- unit map - local map = { - -- date stuff - y = 60 * 60 * 24 * 366, - m = 60 * 60 * 24 * 31, - w = 60 * 60 * 24 * 7, - d = 60 * 60 * 24, - h = 60 * 60, - min = 60, - - -- storage sizes - kb = 1024, - mb = 1024 * 1024, - gb = 1024 * 1024 * 1024, - - -- storage sizes (si) - kib = 1000, - mib = 1000 * 1000, - gib = 1000 * 1000 * 1000 - } - - -- parse input string - for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do - - local num = spec:gsub("[^0-9%.]+$","") - local spn = spec:gsub("^[0-9%.]+", "") - - if map[spn] or map[spn:sub(1,1)] then - val = val + num * ( map[spn] or map[spn:sub(1,1)] ) - else - val = val + num - end - end - - - return val -end - --- also register functions above in the central string class for convenience -string.split = split -string.trim = trim -string.cmatch = cmatch -string.parse_units = parse_units - - -function append(src, ...) - for i, a in ipairs({...}) do - if type(a) == "table" then - for j, v in ipairs(a) do - src[#src+1] = v - end - else - src[#src+1] = a - end - end - return src -end - -function combine(...) - return append({}, ...) -end - -function contains(table, value) - for k, v in pairs(table) do - if value == v then - return k - end - end - return false -end - --- Both table are - in fact - merged together. -function update(t, updates) - for k, v in pairs(updates) do - t[k] = v - end -end - -function keys(t) - local keys = { } - if t then - for k, _ in kspairs(t) do - keys[#keys+1] = k - end - end - return keys -end - -function clone(object, deep) - local copy = {} - - for k, v in pairs(object) do - if deep and type(v) == "table" then - v = clone(v, deep) - end - copy[k] = v - end - - return setmetatable(copy, getmetatable(object)) -end - - --- Serialize the contents of a table value. -function _serialize_table(t, seen) - assert(not seen[t], "Recursion detected.") - seen[t] = true - - local data = "" - local idata = "" - local ilen = 0 - - for k, v in pairs(t) do - if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then - k = serialize_data(k, seen) - v = serialize_data(v, seen) - data = data .. ( #data > 0 and ", " or "" ) .. - '[' .. k .. '] = ' .. v - elseif k > ilen then - ilen = k - end - end - - for i = 1, ilen do - local v = serialize_data(t[i], seen) - idata = idata .. ( #idata > 0 and ", " or "" ) .. v - end - - return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data -end - --- with loadstring(). -function serialize_data(val, seen) - seen = seen or setmetatable({}, {__mode="k"}) - - if val == nil then - return "nil" - elseif type(val) == "number" then - return val - elseif type(val) == "string" then - return "%q" % val - elseif type(val) == "boolean" then - return val and "true" or "false" - elseif type(val) == "function" then - return "loadstring(%q)" % get_bytecode(val) - elseif type(val) == "table" then - return "{ " .. _serialize_table(val, seen) .. " }" - else - return '"[unhandled data type:' .. type(val) .. ']"' - end -end - -function restore_data(str) - return loadstring("return " .. str)() -end - - --- --- Byte code manipulation routines --- - --- will be stripped before it is returned. -function get_bytecode(val) - local code - - if type(val) == "function" then - code = string.dump(val) - else - code = string.dump( loadstring( "return " .. serialize_data(val) ) ) - end - - return code -- and strip_bytecode(code) -end - --- numbers and debugging numbers will be discarded. Original version by --- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html) -function strip_bytecode(code) - local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12) - local subint - if endian == 1 then - subint = function(code, i, l) - local val = 0 - for n = l, 1, -1 do - val = val * 256 + code:byte(i + n - 1) - end - return val, i + l - end - else - subint = function(code, i, l) - local val = 0 - for n = 1, l, 1 do - val = val * 256 + code:byte(i + n - 1) - end - return val, i + l - end - end - - local function strip_function(code) - local count, offset = subint(code, 1, size) - local stripped = { string.rep("\0", size) } - local dirty = offset + count - offset = offset + count + int * 2 + 4 - offset = offset + int + subint(code, offset, int) * ins - count, offset = subint(code, offset, int) - for n = 1, count do - local t - t, offset = subint(code, offset, 1) - if t == 1 then - offset = offset + 1 - elseif t == 4 then - offset = offset + size + subint(code, offset, size) - elseif t == 3 then - offset = offset + num - elseif t == 254 or t == 9 then - offset = offset + lnum - end - end - count, offset = subint(code, offset, int) - stripped[#stripped+1] = code:sub(dirty, offset - 1) - for n = 1, count do - local proto, off = strip_function(code:sub(offset, -1)) - stripped[#stripped+1] = proto - offset = offset + off - 1 - end - offset = offset + subint(code, offset, int) * int + int - count, offset = subint(code, offset, int) - for n = 1, count do - offset = offset + subint(code, offset, size) + size + int * 2 - end - count, offset = subint(code, offset, int) - for n = 1, count do - offset = offset + subint(code, offset, size) + size - end - stripped[#stripped+1] = string.rep("\0", int * 3) - return table.concat(stripped), offset - end - - return code:sub(1,12) .. strip_function(code:sub(13,-1)) -end - - --- --- Sorting iterator functions --- - -function _sortiter( t, f ) - local keys = { } - - local k, v - for k, v in pairs(t) do - keys[#keys+1] = k - end - - local _pos = 0 - - table.sort( keys, f ) - - return function() - _pos = _pos + 1 - if _pos <= #keys then - return keys[_pos], t[keys[_pos]], _pos - end - end -end - --- the provided callback function. -function spairs(t,f) - return _sortiter( t, f ) -end - --- The table pairs are sorted by key. -function kspairs(t) - return _sortiter( t ) -end - --- The table pairs are sorted by value. -function vspairs(t) - return _sortiter( t, function (a,b) return t[a] < t[b] end ) -end - - --- --- System utility functions --- - -function bigendian() - return string.byte(string.dump(function() end), 7) == 0 -end - -function exec(command) - local pp = io.popen(command) - local data = pp:read("*a") - pp:close() - - return data -end - -function execi(command) - local pp = io.popen(command) - - return pp and function() - local line = pp:read() - - if not line then - pp:close() - end - - return line - end -end - --- Deprecated -function execl(command) - local pp = io.popen(command) - local line = "" - local data = {} - - while true do - line = pp:read() - if (line == nil) then break end - data[#data+1] = line - end - pp:close() - - return data -end - - -local ubus_codes = { - "INVALID_COMMAND", - "INVALID_ARGUMENT", - "METHOD_NOT_FOUND", - "NOT_FOUND", - "NO_DATA", - "PERMISSION_DENIED", - "TIMEOUT", - "NOT_SUPPORTED", - "UNKNOWN_ERROR", - "CONNECTION_FAILED" -} - -local function ubus_return(...) - if select('#', ...) == 2 then - local rv, err = select(1, ...), select(2, ...) - if rv == nil and type(err) == "number" then - return nil, err, ubus_codes[err] - end - end - - return ... -end - -function ubus(object, method, data, path, timeout) - if not _ubus_connection then - _ubus_connection = _ubus.connect(path, timeout) - assert(_ubus_connection, "Unable to establish ubus connection") - end - - if object and method then - if type(data) ~= "table" then - data = { } - end - return ubus_return(_ubus_connection:call(object, method, data)) - elseif object then - return _ubus_connection:signatures(object) - else - return _ubus_connection:objects() - end -end - -function serialize_json(x, cb) - local js = json.stringify(x) - if type(cb) == "function" then - cb(js) - else - return js - end -end - - -function libpath() - return require "nixio.fs".dirname(ldebug.__file__) -end - -function checklib(fullpathexe, wantedlib) - local fs = require "nixio.fs" - local haveldd = fs.access('/usr/bin/ldd') - local haveexe = fs.access(fullpathexe) - if not haveldd or not haveexe then - return false - end - local libs = exec(string.format("/usr/bin/ldd %s", shellquote(fullpathexe))) - if not libs then - return false - end - for k, v in ipairs(split(libs)) do - if v:find(wantedlib) then - return true - end - end - return false -end - -------------------------------------------------------------------------------- --- Coroutine safe xpcall and pcall versions --- --- Encapsulates the protected calls with a coroutine based loop, so errors can --- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines --- yielding inside the call to pcall or xpcall. --- --- Authors: Roberto Ierusalimschy and Andre Carregal --- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas --- --- Copyright 2005 - Kepler Project --- --- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $ -------------------------------------------------------------------------------- - -------------------------------------------------------------------------------- --- Implements xpcall with coroutines -------------------------------------------------------------------------------- -local coromap = setmetatable({}, { __mode = "k" }) - -local function handleReturnValue(err, co, status, ...) - if not status then - return false, err(debug.traceback(co, (...)), ...) - end - if coroutine.status(co) == 'suspended' then - return performResume(err, co, coroutine.yield(...)) - else - return true, ... - end -end - -function performResume(err, co, ...) - return handleReturnValue(err, co, coroutine.resume(co, ...)) -end - -local function id(trace, ...) - return trace -end - -function coxpcall(f, err, ...) - local current = coroutine.running() - if not current then - if err == id then - return pcall(f, ...) - else - if select("#", ...) > 0 then - local oldf, params = f, { ... } - f = function() return oldf(unpack(params)) end - end - return xpcall(f, err) - end - else - local res, co = pcall(coroutine.create, f) - if not res then - local newf = function(...) return f(...) end - co = coroutine.create(newf) - end - coromap[co] = current - coxpt[co] = coxpt[current] or current or 0 - return performResume(err, co, ...) - end -end - -function copcall(f, ...) - return coxpcall(f, id, ...) -end diff --git a/modules/luci-base-ucode/root/usr/share/rpcd/ucode/luci b/modules/luci-base-ucode/root/usr/share/rpcd/ucode/luci deleted file mode 100644 index 794676abc6..0000000000 --- a/modules/luci-base-ucode/root/usr/share/rpcd/ucode/luci +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright 2022 Jo-Philipp Wich -// Licensed to the public under the Apache License 2.0. - -'use strict'; - -import { stdin, access, dirname, basename, open, popen, glob, lsdir, readfile, readlink, error } from 'fs'; -import { cursor } from 'uci'; - -import { init_list, init_index, init_enabled, init_action, conntrack_list, process_list } from 'luci.sys'; -import { statvfs } from 'luci.core'; - -import timezones from 'luci.zoneinfo'; - - -function shellquote(s) { - return `'${replace(s, "'", "'\\''")}'`; -} - -const methods = { - getInitList: { - args: { name: 'name' }, - call: function(request) { - let scripts = {}; - - for (let name in filter(init_list(), i => !request.args.name || i == request.args.name)) { - let idx = init_index(name); - - scripts[name] = { - index: idx[0], - stop: idx[1], - enabled: init_enabled(name) - }; - } - - return length(scripts) ? scripts : { error: 'No such init script' }; - } - }, - - setInitAction: { - args: { name: 'name', action: 'action' }, - call: function(request) { - switch (request.args.action) { - case 'enable': - case 'disable': - case 'start': - case 'stop': - case 'restart': - case 'reload': - const rc = init_action(request.args.name, request.args.action); - - if (rc === false) - return { error: 'No such init script' }; - - return { result: rc == 0 }; - - default: - return { error: 'Invalid action' }; - } - } - }, - - getLocaltime: { - call: function(request) { - return { result: time() }; - } - }, - - setLocaltime: { - args: { localtime: 0 }, - call: function(request) { - let t = localtime(request.args.localtime); - - if (t) { - system(sprintf('date -s "%04d-%02d-%02d %02d:%02d:%02d" >/dev/null', t.year, t.mon, t.mday, t.hour, t.min, t.sec)); - system('/etc/init.d/sysfixtime restart >/dev/null'); - } - - return { result: request.args.localtime }; - } - }, - - getTimezones: { - call: function(request) { - let tz = trim(readfile('/etc/TZ')); - let zn = cursor()?.get?.('system', '@system[0]', 'zonename'); - let result = {}; - - for (let zone, tzstring in timezones) { - result[zone] = { tzstring }; - - if (zn == zone) - result[zone].active = true; - }; - - return result; - } - }, - - getLEDs: { - call: function() { - let result = {}; - - for (let led in lsdir('/sys/class/leds')) { - let s; - - result[led] = { triggers: [] }; - - s = trim(readfile(`/sys/class/leds/${led}/trigger`)); - for (let trigger in split(s, ' ')) { - push(result[led].triggers, trim(trigger, '[]')); - - if (trigger != result[led].triggers[-1]) - result[led].active_trigger = result[led].triggers[-1]; - } - - s = readfile(`/sys/class/leds/${led}/brightness`); - result[led].brightness = +s; - - s = readfile(`/sys/class/leds/${led}/max_brightness`); - result[led].max_brightness = +s; - } - - return result; - } - }, - - getUSBDevices: { - call: function() { - let result = { devices: [], ports: [] }; - - for (let path in glob('/sys/bus/usb/devices/[0-9]*/manufacturer')) { - let id = basename(dirname(path)); - - push(result.devices, { - id, - vid: trim(readfile(`/sys/bus/usb/devices/${id}/idVendor`)), - pid: trim(readfile(`/sys/bus/usb/devices/${id}/idProduct`)), - vendor: trim(readfile(path)), - product: trim(readfile(`/sys/bus/usb/devices/${id}/product`)), - speed: +readfile(`/sys/bus/usb/devices/${id}/speed`) - }); - } - - for (let path in glob('/sys/bus/usb/devices/*/*-port[0-9]*')) { - let port = basename(path); - let link = readlink(`${path}/device`); - - push(result.ports, { - port, - device: basename(link) - }); - } - - return result; - } - }, - - getConntrackHelpers: { - call: function() { - const uci = cursor(); - let helpers = []; - - uci.load('/usr/share/firewall4/helpers'); - uci.load('/usr/share/fw3/helpers.conf'); - - uci.foreach('helpers', 'helper', (s) => { - push(helpers, { - name: s.name, - description: s.description, - module: s.module, - family: s.family, - proto: s.proto, - port: s.port - }); - }); - - return { result: helpers }; - } - }, - - getFeatures: { - call: function() { - let result = { - firewall: access('/sbin/fw3') == true, - firewall4: access('/sbin/fw4') == true, - opkg: access('/bin/opkg') == true, - offloading: access('/sys/module/xt_FLOWOFFLOAD/refcnt') == true || access('/sys/module/nft_flow_offload/refcnt') == true, - br2684ctl: access('/usr/sbin/br2684ctl') == true, - swconfig: access('/sbin/swconfig') == true, - odhcpd: access('/usr/sbin/odhcpd') == true, - zram: access('/sys/class/zram-control') == true, - sysntpd: readlink('/usr/sbin/ntpd') != null, - ipv6: access('/proc/net/ipv6_route') == true, - dropbear: access('/usr/sbin/dropbear') == true, - cabundle: access('/etc/ssl/certs/ca-certificates.crt') == true, - relayd: access('/usr/sbin/relayd') == true, - }; - - const wifi_features = [ 'eap', '11n', '11ac', '11r', 'acs', 'sae', 'owe', 'suiteb192', 'wep', 'wps' ]; - - if (access('/usr/sbin/hostapd')) { - result.hostapd = { cli: access('/usr/sbin/hostapd_cli') == true }; - - for (let feature in wifi_features) - result.hostapd[feature] = system(`/usr/sbin/hostapd -v${feature} >/dev/null 2>/dev/null`) == 0; - } - - if (access('/usr/sbin/wpa_supplicant')) { - result.wpasupplicant = { cli: access('/usr/sbin/wpa_cli') == true }; - - for (let feature in wifi_features) - result.wpasupplicant[feature] = system(`/usr/sbin/wpa_supplicant -v${feature} >/dev/null 2>/dev/null`) == 0; - } - - let fd = popen('dnsmasq --version 2>/dev/null'); - - if (fd) { - const m = match(fd.read('all'), /^Compile time options: (.+)$/s); - - for (let opt in split(m?.[1], ' ')) { - let f = replace(opt, 'no-', '', 1); - - result.dnsmasq ??= {}; - result.dnsmasq[lc(f)] = (f == opt); - } - - fd.close(); - } - - fd = popen('ipset --help 2>/dev/null'); - - if (fd) { - for (let line = fd.read('line'), flag = false; length(line); line = fd.read('line')) { - if (line == 'Supported set types:\n') { - flag = true; - } - else if (flag) { - const m = match(line, /^ +([\w:,]+)\t+([0-9]+)\t/); - - if (m) { - result.ipset ??= {}; - result.ipset[m[1]] ??= +m[2]; - } - } - } - - fd.close(); - } - - return result; - } - }, - - getSwconfigFeatures: { - args: { switch: 'switch0' }, - call: function(request) { - // Parse some common switch properties from swconfig help output. - const swc = popen(`swconfig dev ${shellquote(request.args.switch)} help 2>/dev/null`); - - if (swc) { - let is_port_attr = false; - let is_vlan_attr = false; - let result = {}; - - for (let line = swc.read('line'); length(line); line = swc.read('line')) { - if (match(line, /^\s+--vlan/)) { - is_vlan_attr = true; - } - else if (match(line, /^\s+--port/)) { - is_vlan_attr = false; - is_port_attr = true; - } - else if (match(line, /cpu @/)) { - result.switch_title = match(line, /^switch[0-9]+: \w+\((.+)\)/)?.[1]; - result.num_vlans = match(line, /vlans: ([0-9]+)/)?.[1] ?? 16; - result.min_vid = 1; - } - else if (match(line, /: (pvid|tag|vid)/)) { - if (is_vlan_attr) - result.vid_option = match(line, /: (\w+)/)?.[1]; - } - else if (match(line, /: enable_vlan4k/)) { - result.vlan4k_option = 'enable_vlan4k'; - } - else if (match(line, /: enable_vlan/)) { - result.vlan_option = 'enable_vlan'; - } - else if (match(line, /: enable_learning/)) { - result.learning_option = 'enable_learning'; - } - else if (match(line, /: enable_mirror_rx/)) { - result.mirror_option = 'enable_mirror_rx'; - } - else if (match(line, /: max_length/)) { - result.jumbo_option = 'max_length'; - } - } - - swc.close(); - - if (!length(result)) - return { error: 'No such switch' }; - - return result; - } - else { - return { error: error() }; - } - } - }, - - getSwconfigPortState: { - args: { switch: 'switch0' }, - call: function(request) { - const swc = popen(`swconfig dev ${shellquote(request.args.switch)} show 2>/dev/null`); - - if (swc) { - let ports = [], port; - - for (let line = swc.read('line'); length(line); line = swc.read('line')) { - if (match(line, /^VLAN [0-9]+:/) && length(ports)) - break; - - let pnum = match(line, /^Port ([0-9]+):/)?.[1]; - - if (pnum) { - port = { - port: +pnum, - duplex: false, - speed: 0, - link: false, - auto: false, - rxflow: false, - txflow: false - }; - - push(ports, port); - } - - if (port) { - let m; - - if (match(line, /full[ -]duplex/)) - port.duplex = true; - - if ((m = match(line, / speed:([0-9]+)/)) != null) - port.speed = +m[1]; - - if ((m = match(line, /([0-9]+) Mbps/)) != null && !port.speed) - port.speed = +m[1]; - - if ((m = match(line, /link: ([0-9]+)/)) != null && !port.speed) - port.speed = +m[1]; - - if (match(line, /(link|status): ?up/)) - port.link = true; - - if (match(line, /auto-negotiate|link:.*auto/)) - port.auto = true; - - if (match(line, /link:.*rxflow/)) - port.rxflow = true; - - if (match(line, /link:.*txflow/)) - port.txflow = true; - } - } - - swc.close(); - - if (!length(ports)) - return { error: 'No such switch' }; - - return { result: ports }; - } - else { - return { error: error() }; - } - } - }, - - setPassword: { - args: { username: 'root', password: 'password' }, - call: function(request) { - const u = shellquote(request.args.username); - const p = shellquote(request.args.password); - - return { - result: system(`(echo ${p}; sleep 1; echo ${p}) | /bin/busybox passwd ${u} >/dev/null 2>&1`) == 0 - }; - } - }, - - getBlockDevices: { - call: function() { - const block = popen('/sbin/block info 2>/dev/null'); - - if (block) { - let result = {}; - - for (let line = block.read('line'); length(line); line = block.read('line')) { - let dev = match(line, /^\/dev\/([^:]+):/)?.[1]; - - if (dev) { - let e = result[dev] = { - dev: `/dev/${dev}`, - size: +readfile(`/sys/class/block/${dev}/size`) * 512 - }; - - for (m in match(line, / (\w+)="([^"]+)"/g)) - e[lc(m[1])] = m[2]; - } - } - - block.close(); - - return result; - } - else { - return { error: 'Unable to execute block utility' }; - } - } - }, - - setBlockDetect: { - call: function() { - return { result: system('/sbin/block detect > /etc/config/fstab') == 0 }; - } - }, - - getMountPoints: { - call: function() { - const fd = open('/proc/mounts', 'r'); - - if (fd) { - let result = []; - - for (let line = fd.read('line'); length(line); line = fd.read('line')) { - const m = split(line, ' '); - const device = replace(m[0], /\\([0-9][0-9][0-9])/g, (m, n) => char(int(n, 8))); - const mount = replace(m[1], /\\([0-9][0-9][0-9])/g, (m, n) => char(int(n, 8))); - const stat = statvfs(mount); - - if (stat?.blocks > 0) { - push(result, { - device, mount, - size: stat.bsize * stat.blocks, - avail: stat.bsize * stat.bavail, - free: stat.bsize * stat.bfree - }); - } - } - - fd.close(); - - return { result }; - } - else { - return { error: error() }; - } - } - }, - getRealtimeStats: { - args: { mode: 'interface', device: 'eth0' }, - call: function(request) { - let flags; - - if (request.args.mode == 'interface') - flags = `-i ${shellquote(request.args.device)}`; - else if (request.args.mode == 'wireless') - flags = `-r ${shellquote(request.args.device)}`; - else if (request.args.mode == 'conntrack') - flags = '-c'; - else if (request.args.mode == 'load') - flags = '-l'; - else - return { error: 'Invalid mode' }; - - const fd = popen(`luci-bwc ${flags}`, 'r'); - - if (fd) { - let result; - - try { - result = { result: json(`[${fd.read('all')}]`) }; - } - catch (err) { - result = { error: err }; - } - - return result; - } - else { - return { error: error() }; - } - } - }, - - getConntrackList: { - call: function() { - return { result: conntrack_list() }; - } - }, - - getProcessList: { - call: function() { - return { result: process_list() }; - } - } -}; - -return { luci: methods }; diff --git a/modules/luci-base-ucode/src/Makefile b/modules/luci-base-ucode/src/Makefile deleted file mode 100644 index 8aeb29aee4..0000000000 --- a/modules/luci-base-ucode/src/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -%.o: %.c - $(CC) $(CPPFLAGS) $(CFLAGS) $(FPIC) -DNDEBUG -c -o $@ $< - -contrib/lemon: contrib/lemon.c contrib/lempar.c - cc -o contrib/lemon $< - -lib/plural_formula.c: lib/plural_formula.y contrib/lemon - ./contrib/lemon -q $< - -lib/lmo.c: lib/plural_formula.c - -core.so: lib/luci.o lib/lmo.o lib/plural_formula.o - $(CC) $(LDFLAGS) -shared -o $@ $^ - -version.uc: - echo "export const revision = '$(LUCI_VERSION)', branch = '$(LUCI_GITBRANCH)';" > $@ - -clean: - rm -f contrib/lemon lib/*.o lib/plural_formula.c lib/plural_formula.h core.so version.uc - -compile: core.so version.uc - -install: compile - mkdir -p $(DESTDIR)/usr/lib/ucode/luci - cp core.so $(DESTDIR)/usr/lib/ucode/luci/core.so - - mkdir -p $(DESTDIR)/usr/share/ucode/luci - cp version.uc $(DESTDIR)/usr/share/ucode/luci/version.uc diff --git a/modules/luci-base-ucode/src/contrib/lemon.c b/modules/luci-base-ucode/src/contrib/lemon.c deleted file mode 100644 index 85e94f7007..0000000000 --- a/modules/luci-base-ucode/src/contrib/lemon.c +++ /dev/null @@ -1,5040 +0,0 @@ -/* -** This file contains all sources (including headers) to the LEMON -** LALR(1) parser generator. The sources have been combined into a -** single file to make it easy to include LEMON in the source tree -** and Makefile of another program. -** -** The author of this program disclaims copyright. -*/ -#include -#include -#include -#include -#include -#include - -#ifndef __WIN32__ -# if defined(_WIN32) || defined(WIN32) -# define __WIN32__ -# endif -#endif - -#ifdef __WIN32__ -#ifdef __cplusplus -extern "C" { -#endif -extern int access(const char *path, int mode); -#ifdef __cplusplus -} -#endif -#else -#include -#endif - -/* #define PRIVATE static */ -#define PRIVATE - -#ifdef TEST -#define MAXRHS 5 /* Set low to exercise exception code */ -#else -#define MAXRHS 1000 -#endif - -static int showPrecedenceConflict = 0; -static char *msort(char*,char**,int(*)(const char*,const char*)); - -/* -** Compilers are getting increasingly pedantic about type conversions -** as C evolves ever closer to Ada.... To work around the latest problems -** we have to define the following variant of strlen(). -*/ -#define lemonStrlen(X) ((int)strlen(X)) - -/* -** Compilers are starting to complain about the use of sprintf() and strcpy(), -** saying they are unsafe. So we define our own versions of those routines too. -** -** There are three routines here: lemon_sprintf(), lemon_vsprintf(), and -** lemon_addtext(). The first two are replacements for sprintf() and vsprintf(). -** The third is a helper routine for vsnprintf() that adds texts to the end of a -** buffer, making sure the buffer is always zero-terminated. -** -** The string formatter is a minimal subset of stdlib sprintf() supporting only -** a few simply conversions: -** -** %d -** %s -** %.*s -** -*/ -static void lemon_addtext( - char *zBuf, /* The buffer to which text is added */ - int *pnUsed, /* Slots of the buffer used so far */ - const char *zIn, /* Text to add */ - int nIn, /* Bytes of text to add. -1 to use strlen() */ - int iWidth /* Field width. Negative to left justify */ -){ - if( nIn<0 ) for(nIn=0; zIn[nIn]; nIn++){} - while( iWidth>nIn ){ zBuf[(*pnUsed)++] = ' '; iWidth--; } - if( nIn==0 ) return; - memcpy(&zBuf[*pnUsed], zIn, nIn); - *pnUsed += nIn; - while( (-iWidth)>nIn ){ zBuf[(*pnUsed)++] = ' '; iWidth++; } - zBuf[*pnUsed] = 0; -} -static int lemon_vsprintf(char *str, const char *zFormat, va_list ap){ - int i, j, k, c; - int nUsed = 0; - const char *z; - char zTemp[50]; - str[0] = 0; - for(i=j=0; (c = zFormat[i])!=0; i++){ - if( c=='%' ){ - int iWidth = 0; - lemon_addtext(str, &nUsed, &zFormat[j], i-j, 0); - c = zFormat[++i]; - if( isdigit(c) || (c=='-' && isdigit(zFormat[i+1])) ){ - if( c=='-' ) i++; - while( isdigit(zFormat[i]) ) iWidth = iWidth*10 + zFormat[i++] - '0'; - if( c=='-' ) iWidth = -iWidth; - c = zFormat[i]; - } - if( c=='d' ){ - int v = va_arg(ap, int); - if( v<0 ){ - lemon_addtext(str, &nUsed, "-", 1, iWidth); - v = -v; - }else if( v==0 ){ - lemon_addtext(str, &nUsed, "0", 1, iWidth); - } - k = 0; - while( v>0 ){ - k++; - zTemp[sizeof(zTemp)-k] = (v%10) + '0'; - v /= 10; - } - lemon_addtext(str, &nUsed, &zTemp[sizeof(zTemp)-k], k, iWidth); - }else if( c=='s' ){ - z = va_arg(ap, const char*); - lemon_addtext(str, &nUsed, z, -1, iWidth); - }else if( c=='.' && memcmp(&zFormat[i], ".*s", 3)==0 ){ - i += 2; - k = va_arg(ap, int); - z = va_arg(ap, const char*); - lemon_addtext(str, &nUsed, z, k, iWidth); - }else if( c=='%' ){ - lemon_addtext(str, &nUsed, "%", 1, 0); - }else{ - fprintf(stderr, "illegal format\n"); - exit(1); - } - j = i+1; - } - } - lemon_addtext(str, &nUsed, &zFormat[j], i-j, 0); - return nUsed; -} -static int lemon_sprintf(char *str, const char *format, ...){ - va_list ap; - int rc; - va_start(ap, format); - rc = lemon_vsprintf(str, format, ap); - va_end(ap); - return rc; -} -static void lemon_strcpy(char *dest, const char *src){ - while( (*(dest++) = *(src++))!=0 ){} -} -static void lemon_strcat(char *dest, const char *src){ - while( *dest ) dest++; - lemon_strcpy(dest, src); -} - - -/* a few forward declarations... */ -struct rule; -struct lemon; -struct action; - -static struct action *Action_new(void); -static struct action *Action_sort(struct action *); - -/********** From the file "build.h" ************************************/ -void FindRulePrecedences(); -void FindFirstSets(); -void FindStates(); -void FindLinks(); -void FindFollowSets(); -void FindActions(); - -/********* From the file "configlist.h" *********************************/ -void Configlist_init(void); -struct config *Configlist_add(struct rule *, int); -struct config *Configlist_addbasis(struct rule *, int); -void Configlist_closure(struct lemon *); -void Configlist_sort(void); -void Configlist_sortbasis(void); -struct config *Configlist_return(void); -struct config *Configlist_basis(void); -void Configlist_eat(struct config *); -void Configlist_reset(void); - -/********* From the file "error.h" ***************************************/ -void ErrorMsg(const char *, int,const char *, ...); - -/****** From the file "option.h" ******************************************/ -enum option_type { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR, - OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR}; -struct s_options { - enum option_type type; - const char *label; - char *arg; - const char *message; -}; -int OptInit(char**,struct s_options*,FILE*); -int OptNArgs(void); -char *OptArg(int); -void OptErr(int); -void OptPrint(void); - -/******** From the file "parse.h" *****************************************/ -void Parse(struct lemon *lemp); - -/********* From the file "plink.h" ***************************************/ -struct plink *Plink_new(void); -void Plink_add(struct plink **, struct config *); -void Plink_copy(struct plink **, struct plink *); -void Plink_delete(struct plink *); - -/********** From the file "report.h" *************************************/ -void Reprint(struct lemon *); -void ReportOutput(struct lemon *); -void ReportTable(struct lemon *, int); -void ReportHeader(struct lemon *); -void CompressTables(struct lemon *); -void ResortStates(struct lemon *); - -/********** From the file "set.h" ****************************************/ -void SetSize(int); /* All sets will be of size N */ -char *SetNew(void); /* A new set for element 0..N */ -void SetFree(char*); /* Deallocate a set */ -int SetAdd(char*,int); /* Add element to a set */ -int SetUnion(char *,char *); /* A <- A U B, thru element N */ -#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */ - -/********** From the file "struct.h" *************************************/ -/* -** Principal data structures for the LEMON parser generator. -*/ - -typedef enum {LEMON_FALSE=0, LEMON_TRUE} Boolean; - -/* Symbols (terminals and nonterminals) of the grammar are stored -** in the following: */ -enum symbol_type { - TERMINAL, - NONTERMINAL, - MULTITERMINAL -}; -enum e_assoc { - LEFT, - RIGHT, - NONE, - UNK -}; -struct symbol { - const char *name; /* Name of the symbol */ - int index; /* Index number for this symbol */ - enum symbol_type type; /* Symbols are all either TERMINALS or NTs */ - struct rule *rule; /* Linked list of rules of this (if an NT) */ - struct symbol *fallback; /* fallback token in case this token doesn't parse */ - int prec; /* Precedence if defined (-1 otherwise) */ - enum e_assoc assoc; /* Associativity if precedence is defined */ - char *firstset; /* First-set for all rules of this symbol */ - Boolean lambda; /* True if NT and can generate an empty string */ - int useCnt; /* Number of times used */ - char *destructor; /* Code which executes whenever this symbol is - ** popped from the stack during error processing */ - int destLineno; /* Line number for start of destructor */ - char *datatype; /* The data type of information held by this - ** object. Only used if type==NONTERMINAL */ - int dtnum; /* The data type number. In the parser, the value - ** stack is a union. The .yy%d element of this - ** union is the correct data type for this object */ - /* The following fields are used by MULTITERMINALs only */ - int nsubsym; /* Number of constituent symbols in the MULTI */ - struct symbol **subsym; /* Array of constituent symbols */ -}; - -/* Each production rule in the grammar is stored in the following -** structure. */ -struct rule { - struct symbol *lhs; /* Left-hand side of the rule */ - const char *lhsalias; /* Alias for the LHS (NULL if none) */ - int lhsStart; /* True if left-hand side is the start symbol */ - int ruleline; /* Line number for the rule */ - int nrhs; /* Number of RHS symbols */ - struct symbol **rhs; /* The RHS symbols */ - const char **rhsalias; /* An alias for each RHS symbol (NULL if none) */ - int line; /* Line number at which code begins */ - const char *code; /* The code executed when this rule is reduced */ - struct symbol *precsym; /* Precedence symbol for this rule */ - int index; /* An index number for this rule */ - Boolean canReduce; /* True if this rule is ever reduced */ - struct rule *nextlhs; /* Next rule with the same LHS */ - struct rule *next; /* Next rule in the global list */ -}; - -/* A configuration is a production rule of the grammar together with -** a mark (dot) showing how much of that rule has been processed so far. -** Configurations also contain a follow-set which is a list of terminal -** symbols which are allowed to immediately follow the end of the rule. -** Every configuration is recorded as an instance of the following: */ -enum cfgstatus { - COMPLETE, - INCOMPLETE -}; -struct config { - struct rule *rp; /* The rule upon which the configuration is based */ - int dot; /* The parse point */ - char *fws; /* Follow-set for this configuration only */ - struct plink *fplp; /* Follow-set forward propagation links */ - struct plink *bplp; /* Follow-set backwards propagation links */ - struct state *stp; /* Pointer to state which contains this */ - enum cfgstatus status; /* used during followset and shift computations */ - struct config *next; /* Next configuration in the state */ - struct config *bp; /* The next basis configuration */ -}; - -enum e_action { - SHIFT, - ACCEPT, - REDUCE, - ERROR, - SSCONFLICT, /* A shift/shift conflict */ - SRCONFLICT, /* Was a reduce, but part of a conflict */ - RRCONFLICT, /* Was a reduce, but part of a conflict */ - SH_RESOLVED, /* Was a shift. Precedence resolved conflict */ - RD_RESOLVED, /* Was reduce. Precedence resolved conflict */ - NOT_USED /* Deleted by compression */ -}; - -/* Every shift or reduce operation is stored as one of the following */ -struct action { - struct symbol *sp; /* The look-ahead symbol */ - enum e_action type; - union { - struct state *stp; /* The new state, if a shift */ - struct rule *rp; /* The rule, if a reduce */ - } x; - struct action *next; /* Next action for this state */ - struct action *collide; /* Next action with the same hash */ -}; - -/* Each state of the generated parser's finite state machine -** is encoded as an instance of the following structure. */ -struct state { - struct config *bp; /* The basis configurations for this state */ - struct config *cfp; /* All configurations in this set */ - int statenum; /* Sequential number for this state */ - struct action *ap; /* Array of actions for this state */ - int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */ - int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */ - int iDflt; /* Default action */ -}; -#define NO_OFFSET (-2147483647) - -/* A followset propagation link indicates that the contents of one -** configuration followset should be propagated to another whenever -** the first changes. */ -struct plink { - struct config *cfp; /* The configuration to which linked */ - struct plink *next; /* The next propagate link */ -}; - -/* The state vector for the entire parser generator is recorded as -** follows. (LEMON uses no global variables and makes little use of -** static variables. Fields in the following structure can be thought -** of as begin global variables in the program.) */ -struct lemon { - struct state **sorted; /* Table of states sorted by state number */ - struct rule *rule; /* List of all rules */ - int nstate; /* Number of states */ - int nrule; /* Number of rules */ - int nsymbol; /* Number of terminal and nonterminal symbols */ - int nterminal; /* Number of terminal symbols */ - struct symbol **symbols; /* Sorted array of pointers to symbols */ - int errorcnt; /* Number of errors */ - struct symbol *errsym; /* The error symbol */ - struct symbol *wildcard; /* Token that matches anything */ - char *name; /* Name of the generated parser */ - char *arg; /* Declaration of the 3th argument to parser */ - char *tokentype; /* Type of terminal symbols in the parser stack */ - char *vartype; /* The default type of non-terminal symbols */ - char *start; /* Name of the start symbol for the grammar */ - char *stacksize; /* Size of the parser stack */ - char *include; /* Code to put at the start of the C file */ - char *error; /* Code to execute when an error is seen */ - char *overflow; /* Code to execute on a stack overflow */ - char *failure; /* Code to execute on parser failure */ - char *accept; /* Code to execute when the parser excepts */ - char *extracode; /* Code appended to the generated file */ - char *tokendest; /* Code to execute to destroy token data */ - char *vardest; /* Code for the default non-terminal destructor */ - char *filename; /* Name of the input file */ - char *outname; /* Name of the current output file */ - char *tokenprefix; /* A prefix added to token names in the .h file */ - int nconflict; /* Number of parsing conflicts */ - int tablesize; /* Size of the parse tables */ - int basisflag; /* Print only basis configurations */ - int has_fallback; /* True if any %fallback is seen in the grammar */ - int nolinenosflag; /* True if #line statements should not be printed */ - char *argv0; /* Name of the program */ -}; - -#define MemoryCheck(X) if((X)==0){ \ - extern void memory_error(); \ - memory_error(); \ -} - -/**************** From the file "table.h" *********************************/ -/* -** All code in this file has been automatically generated -** from a specification in the file -** "table.q" -** by the associative array code building program "aagen". -** Do not edit this file! Instead, edit the specification -** file, then rerun aagen. -*/ -/* -** Code for processing tables in the LEMON parser generator. -*/ -/* Routines for handling a strings */ - -const char *Strsafe(const char *); - -void Strsafe_init(void); -int Strsafe_insert(const char *); -const char *Strsafe_find(const char *); - -/* Routines for handling symbols of the grammar */ - -struct symbol *Symbol_new(const char *); -int Symbolcmpp(const void *, const void *); -void Symbol_init(void); -int Symbol_insert(struct symbol *, const char *); -struct symbol *Symbol_find(const char *); -struct symbol *Symbol_Nth(int); -int Symbol_count(void); -struct symbol **Symbol_arrayof(void); - -/* Routines to manage the state table */ - -int Configcmp(const char *, const char *); -struct state *State_new(void); -void State_init(void); -int State_insert(struct state *, struct config *); -struct state *State_find(struct config *); -struct state **State_arrayof(/* */); - -/* Routines used for efficiency in Configlist_add */ - -void Configtable_init(void); -int Configtable_insert(struct config *); -struct config *Configtable_find(struct config *); -void Configtable_clear(int(*)(struct config *)); - -/****************** From the file "action.c" *******************************/ -/* -** Routines processing parser actions in the LEMON parser generator. -*/ - -/* Allocate a new parser action */ -static struct action *Action_new(void){ - static struct action *freelist = 0; - struct action *newaction; - - if( freelist==0 ){ - int i; - int amt = 100; - freelist = (struct action *)calloc(amt, sizeof(struct action)); - if( freelist==0 ){ - fprintf(stderr,"Unable to allocate memory for a new parser action."); - exit(1); - } - for(i=0; inext; - return newaction; -} - -/* Compare two actions for sorting purposes. Return negative, zero, or -** positive if the first action is less than, equal to, or greater than -** the first -*/ -static int actioncmp( - struct action *ap1, - struct action *ap2 -){ - int rc; - rc = ap1->sp->index - ap2->sp->index; - if( rc==0 ){ - rc = (int)ap1->type - (int)ap2->type; - } - if( rc==0 && ap1->type==REDUCE ){ - rc = ap1->x.rp->index - ap2->x.rp->index; - } - if( rc==0 ){ - rc = (int) (ap2 - ap1); - } - return rc; -} - -/* Sort parser actions */ -static struct action *Action_sort( - struct action *ap -){ - ap = (struct action *)msort((char *)ap,(char **)&ap->next, - (int(*)(const char*,const char*))actioncmp); - return ap; -} - -void Action_add( - struct action **app, - enum e_action type, - struct symbol *sp, - char *arg -){ - struct action *newaction; - newaction = Action_new(); - newaction->next = *app; - *app = newaction; - newaction->type = type; - newaction->sp = sp; - if( type==SHIFT ){ - newaction->x.stp = (struct state *)arg; - }else{ - newaction->x.rp = (struct rule *)arg; - } -} -/********************** New code to implement the "acttab" module ***********/ -/* -** This module implements routines use to construct the yy_action[] table. -*/ - -/* -** The state of the yy_action table under construction is an instance of -** the following structure. -** -** The yy_action table maps the pair (state_number, lookahead) into an -** action_number. The table is an array of integers pairs. The state_number -** determines an initial offset into the yy_action array. The lookahead -** value is then added to this initial offset to get an index X into the -** yy_action array. If the aAction[X].lookahead equals the value of the -** of the lookahead input, then the value of the action_number output is -** aAction[X].action. If the lookaheads do not match then the -** default action for the state_number is returned. -** -** All actions associated with a single state_number are first entered -** into aLookahead[] using multiple calls to acttab_action(). Then the -** actions for that single state_number are placed into the aAction[] -** array with a single call to acttab_insert(). The acttab_insert() call -** also resets the aLookahead[] array in preparation for the next -** state number. -*/ -struct lookahead_action { - int lookahead; /* Value of the lookahead token */ - int action; /* Action to take on the given lookahead */ -}; -typedef struct acttab acttab; -struct acttab { - int nAction; /* Number of used slots in aAction[] */ - int nActionAlloc; /* Slots allocated for aAction[] */ - struct lookahead_action - *aAction, /* The yy_action[] table under construction */ - *aLookahead; /* A single new transaction set */ - int mnLookahead; /* Minimum aLookahead[].lookahead */ - int mnAction; /* Action associated with mnLookahead */ - int mxLookahead; /* Maximum aLookahead[].lookahead */ - int nLookahead; /* Used slots in aLookahead[] */ - int nLookaheadAlloc; /* Slots allocated in aLookahead[] */ -}; - -/* Return the number of entries in the yy_action table */ -#define acttab_size(X) ((X)->nAction) - -/* The value for the N-th entry in yy_action */ -#define acttab_yyaction(X,N) ((X)->aAction[N].action) - -/* The value for the N-th entry in yy_lookahead */ -#define acttab_yylookahead(X,N) ((X)->aAction[N].lookahead) - -/* Free all memory associated with the given acttab */ -void acttab_free(acttab *p){ - free( p->aAction ); - free( p->aLookahead ); - free( p ); -} - -/* Allocate a new acttab structure */ -acttab *acttab_alloc(void){ - acttab *p = (acttab *) calloc( 1, sizeof(*p) ); - if( p==0 ){ - fprintf(stderr,"Unable to allocate memory for a new acttab."); - exit(1); - } - memset(p, 0, sizeof(*p)); - return p; -} - -/* Add a new action to the current transaction set. -** -** This routine is called once for each lookahead for a particular -** state. -*/ -void acttab_action(acttab *p, int lookahead, int action){ - if( p->nLookahead>=p->nLookaheadAlloc ){ - p->nLookaheadAlloc += 25; - p->aLookahead = (struct lookahead_action *) realloc( p->aLookahead, - sizeof(p->aLookahead[0])*p->nLookaheadAlloc ); - if( p->aLookahead==0 ){ - fprintf(stderr,"malloc failed\n"); - exit(1); - } - } - if( p->nLookahead==0 ){ - p->mxLookahead = lookahead; - p->mnLookahead = lookahead; - p->mnAction = action; - }else{ - if( p->mxLookaheadmxLookahead = lookahead; - if( p->mnLookahead>lookahead ){ - p->mnLookahead = lookahead; - p->mnAction = action; - } - } - p->aLookahead[p->nLookahead].lookahead = lookahead; - p->aLookahead[p->nLookahead].action = action; - p->nLookahead++; -} - -/* -** Add the transaction set built up with prior calls to acttab_action() -** into the current action table. Then reset the transaction set back -** to an empty set in preparation for a new round of acttab_action() calls. -** -** Return the offset into the action table of the new transaction. -*/ -int acttab_insert(acttab *p){ - int i, j, k, n; - assert( p->nLookahead>0 ); - - /* Make sure we have enough space to hold the expanded action table - ** in the worst case. The worst case occurs if the transaction set - ** must be appended to the current action table - */ - n = p->mxLookahead + 1; - if( p->nAction + n >= p->nActionAlloc ){ - int oldAlloc = p->nActionAlloc; - p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20; - p->aAction = (struct lookahead_action *) realloc( p->aAction, - sizeof(p->aAction[0])*p->nActionAlloc); - if( p->aAction==0 ){ - fprintf(stderr,"malloc failed\n"); - exit(1); - } - for(i=oldAlloc; inActionAlloc; i++){ - p->aAction[i].lookahead = -1; - p->aAction[i].action = -1; - } - } - - /* Scan the existing action table looking for an offset that is a - ** duplicate of the current transaction set. Fall out of the loop - ** if and when the duplicate is found. - ** - ** i is the index in p->aAction[] where p->mnLookahead is inserted. - */ - for(i=p->nAction-1; i>=0; i--){ - if( p->aAction[i].lookahead==p->mnLookahead ){ - /* All lookaheads and actions in the aLookahead[] transaction - ** must match against the candidate aAction[i] entry. */ - if( p->aAction[i].action!=p->mnAction ) continue; - for(j=0; jnLookahead; j++){ - k = p->aLookahead[j].lookahead - p->mnLookahead + i; - if( k<0 || k>=p->nAction ) break; - if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break; - if( p->aLookahead[j].action!=p->aAction[k].action ) break; - } - if( jnLookahead ) continue; - - /* No possible lookahead value that is not in the aLookahead[] - ** transaction is allowed to match aAction[i] */ - n = 0; - for(j=0; jnAction; j++){ - if( p->aAction[j].lookahead<0 ) continue; - if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++; - } - if( n==p->nLookahead ){ - break; /* An exact match is found at offset i */ - } - } - } - - /* If no existing offsets exactly match the current transaction, find an - ** an empty offset in the aAction[] table in which we can add the - ** aLookahead[] transaction. - */ - if( i<0 ){ - /* Look for holes in the aAction[] table that fit the current - ** aLookahead[] transaction. Leave i set to the offset of the hole. - ** If no holes are found, i is left at p->nAction, which means the - ** transaction will be appended. */ - for(i=0; inActionAlloc - p->mxLookahead; i++){ - if( p->aAction[i].lookahead<0 ){ - for(j=0; jnLookahead; j++){ - k = p->aLookahead[j].lookahead - p->mnLookahead + i; - if( k<0 ) break; - if( p->aAction[k].lookahead>=0 ) break; - } - if( jnLookahead ) continue; - for(j=0; jnAction; j++){ - if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break; - } - if( j==p->nAction ){ - break; /* Fits in empty slots */ - } - } - } - } - /* Insert transaction set at index i. */ - for(j=0; jnLookahead; j++){ - k = p->aLookahead[j].lookahead - p->mnLookahead + i; - p->aAction[k] = p->aLookahead[j]; - if( k>=p->nAction ) p->nAction = k+1; - } - p->nLookahead = 0; - - /* Return the offset that is added to the lookahead in order to get the - ** index into yy_action of the action */ - return i - p->mnLookahead; -} - -/********************** From the file "build.c" *****************************/ -/* -** Routines to construction the finite state machine for the LEMON -** parser generator. -*/ - -/* Find a precedence symbol of every rule in the grammar. -** -** Those rules which have a precedence symbol coded in the input -** grammar using the "[symbol]" construct will already have the -** rp->precsym field filled. Other rules take as their precedence -** symbol the first RHS symbol with a defined precedence. If there -** are not RHS symbols with a defined precedence, the precedence -** symbol field is left blank. -*/ -void FindRulePrecedences(struct lemon *xp) -{ - struct rule *rp; - for(rp=xp->rule; rp; rp=rp->next){ - if( rp->precsym==0 ){ - int i, j; - for(i=0; inrhs && rp->precsym==0; i++){ - struct symbol *sp = rp->rhs[i]; - if( sp->type==MULTITERMINAL ){ - for(j=0; jnsubsym; j++){ - if( sp->subsym[j]->prec>=0 ){ - rp->precsym = sp->subsym[j]; - break; - } - } - }else if( sp->prec>=0 ){ - rp->precsym = rp->rhs[i]; - } - } - } - } - return; -} - -/* Find all nonterminals which will generate the empty string. -** Then go back and compute the first sets of every nonterminal. -** The first set is the set of all terminal symbols which can begin -** a string generated by that nonterminal. -*/ -void FindFirstSets(struct lemon *lemp) -{ - int i, j; - struct rule *rp; - int progress; - - for(i=0; insymbol; i++){ - lemp->symbols[i]->lambda = LEMON_FALSE; - } - for(i=lemp->nterminal; insymbol; i++){ - lemp->symbols[i]->firstset = SetNew(); - } - - /* First compute all lambdas */ - do{ - progress = 0; - for(rp=lemp->rule; rp; rp=rp->next){ - if( rp->lhs->lambda ) continue; - for(i=0; inrhs; i++){ - struct symbol *sp = rp->rhs[i]; - assert( sp->type==NONTERMINAL || sp->lambda==LEMON_FALSE ); - if( sp->lambda==LEMON_FALSE ) break; - } - if( i==rp->nrhs ){ - rp->lhs->lambda = LEMON_TRUE; - progress = 1; - } - } - }while( progress ); - - /* Now compute all first sets */ - do{ - struct symbol *s1, *s2; - progress = 0; - for(rp=lemp->rule; rp; rp=rp->next){ - s1 = rp->lhs; - for(i=0; inrhs; i++){ - s2 = rp->rhs[i]; - if( s2->type==TERMINAL ){ - progress += SetAdd(s1->firstset,s2->index); - break; - }else if( s2->type==MULTITERMINAL ){ - for(j=0; jnsubsym; j++){ - progress += SetAdd(s1->firstset,s2->subsym[j]->index); - } - break; - }else if( s1==s2 ){ - if( s1->lambda==LEMON_FALSE ) break; - }else{ - progress += SetUnion(s1->firstset,s2->firstset); - if( s2->lambda==LEMON_FALSE ) break; - } - } - } - }while( progress ); - return; -} - -/* Compute all LR(0) states for the grammar. Links -** are added to between some states so that the LR(1) follow sets -** can be computed later. -*/ -PRIVATE struct state *getstate(struct lemon *); /* forward reference */ -void FindStates(struct lemon *lemp) -{ - struct symbol *sp; - struct rule *rp; - - Configlist_init(); - - /* Find the start symbol */ - if( lemp->start ){ - sp = Symbol_find(lemp->start); - if( sp==0 ){ - ErrorMsg(lemp->filename,0, -"The specified start symbol \"%s\" is not \ -in a nonterminal of the grammar. \"%s\" will be used as the start \ -symbol instead.",lemp->start,lemp->rule->lhs->name); - lemp->errorcnt++; - sp = lemp->rule->lhs; - } - }else{ - sp = lemp->rule->lhs; - } - - /* Make sure the start symbol doesn't occur on the right-hand side of - ** any rule. Report an error if it does. (YACC would generate a new - ** start symbol in this case.) */ - for(rp=lemp->rule; rp; rp=rp->next){ - int i; - for(i=0; inrhs; i++){ - if( rp->rhs[i]==sp ){ /* FIX ME: Deal with multiterminals */ - ErrorMsg(lemp->filename,0, -"The start symbol \"%s\" occurs on the \ -right-hand side of a rule. This will result in a parser which \ -does not work properly.",sp->name); - lemp->errorcnt++; - } - } - } - - /* The basis configuration set for the first state - ** is all rules which have the start symbol as their - ** left-hand side */ - for(rp=sp->rule; rp; rp=rp->nextlhs){ - struct config *newcfp; - rp->lhsStart = 1; - newcfp = Configlist_addbasis(rp,0); - SetAdd(newcfp->fws,0); - } - - /* Compute the first state. All other states will be - ** computed automatically during the computation of the first one. - ** The returned pointer to the first state is not used. */ - (void)getstate(lemp); - return; -} - -/* Return a pointer to a state which is described by the configuration -** list which has been built from calls to Configlist_add. -*/ -PRIVATE void buildshifts(struct lemon *, struct state *); /* Forwd ref */ -PRIVATE struct state *getstate(struct lemon *lemp) -{ - struct config *cfp, *bp; - struct state *stp; - - /* Extract the sorted basis of the new state. The basis was constructed - ** by prior calls to "Configlist_addbasis()". */ - Configlist_sortbasis(); - bp = Configlist_basis(); - - /* Get a state with the same basis */ - stp = State_find(bp); - if( stp ){ - /* A state with the same basis already exists! Copy all the follow-set - ** propagation links from the state under construction into the - ** preexisting state, then return a pointer to the preexisting state */ - struct config *x, *y; - for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){ - Plink_copy(&y->bplp,x->bplp); - Plink_delete(x->fplp); - x->fplp = x->bplp = 0; - } - cfp = Configlist_return(); - Configlist_eat(cfp); - }else{ - /* This really is a new state. Construct all the details */ - Configlist_closure(lemp); /* Compute the configuration closure */ - Configlist_sort(); /* Sort the configuration closure */ - cfp = Configlist_return(); /* Get a pointer to the config list */ - stp = State_new(); /* A new state structure */ - MemoryCheck(stp); - stp->bp = bp; /* Remember the configuration basis */ - stp->cfp = cfp; /* Remember the configuration closure */ - stp->statenum = lemp->nstate++; /* Every state gets a sequence number */ - stp->ap = 0; /* No actions, yet. */ - State_insert(stp,stp->bp); /* Add to the state table */ - buildshifts(lemp,stp); /* Recursively compute successor states */ - } - return stp; -} - -/* -** Return true if two symbols are the same. -*/ -int same_symbol(struct symbol *a, struct symbol *b) -{ - int i; - if( a==b ) return 1; - if( a->type!=MULTITERMINAL ) return 0; - if( b->type!=MULTITERMINAL ) return 0; - if( a->nsubsym!=b->nsubsym ) return 0; - for(i=0; insubsym; i++){ - if( a->subsym[i]!=b->subsym[i] ) return 0; - } - return 1; -} - -/* Construct all successor states to the given state. A "successor" -** state is any state which can be reached by a shift action. -*/ -PRIVATE void buildshifts(struct lemon *lemp, struct state *stp) -{ - struct config *cfp; /* For looping thru the config closure of "stp" */ - struct config *bcfp; /* For the inner loop on config closure of "stp" */ - struct config *newcfg; /* */ - struct symbol *sp; /* Symbol following the dot in configuration "cfp" */ - struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */ - struct state *newstp; /* A pointer to a successor state */ - - /* Each configuration becomes complete after it contibutes to a successor - ** state. Initially, all configurations are incomplete */ - for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE; - - /* Loop through all configurations of the state "stp" */ - for(cfp=stp->cfp; cfp; cfp=cfp->next){ - if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */ - if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */ - Configlist_reset(); /* Reset the new config set */ - sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */ - - /* For every configuration in the state "stp" which has the symbol "sp" - ** following its dot, add the same configuration to the basis set under - ** construction but with the dot shifted one symbol to the right. */ - for(bcfp=cfp; bcfp; bcfp=bcfp->next){ - if( bcfp->status==COMPLETE ) continue; /* Already used */ - if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */ - bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */ - if( !same_symbol(bsp,sp) ) continue; /* Must be same as for "cfp" */ - bcfp->status = COMPLETE; /* Mark this config as used */ - newcfg = Configlist_addbasis(bcfp->rp,bcfp->dot+1); - Plink_add(&newcfg->bplp,bcfp); - } - - /* Get a pointer to the state described by the basis configuration set - ** constructed in the preceding loop */ - newstp = getstate(lemp); - - /* The state "newstp" is reached from the state "stp" by a shift action - ** on the symbol "sp" */ - if( sp->type==MULTITERMINAL ){ - int i; - for(i=0; insubsym; i++){ - Action_add(&stp->ap,SHIFT,sp->subsym[i],(char*)newstp); - } - }else{ - Action_add(&stp->ap,SHIFT,sp,(char *)newstp); - } - } -} - -/* -** Construct the propagation links -*/ -void FindLinks(struct lemon *lemp) -{ - int i; - struct config *cfp, *other; - struct state *stp; - struct plink *plp; - - /* Housekeeping detail: - ** Add to every propagate link a pointer back to the state to - ** which the link is attached. */ - for(i=0; instate; i++){ - stp = lemp->sorted[i]; - for(cfp=stp->cfp; cfp; cfp=cfp->next){ - cfp->stp = stp; - } - } - - /* Convert all backlinks into forward links. Only the forward - ** links are used in the follow-set computation. */ - for(i=0; instate; i++){ - stp = lemp->sorted[i]; - for(cfp=stp->cfp; cfp; cfp=cfp->next){ - for(plp=cfp->bplp; plp; plp=plp->next){ - other = plp->cfp; - Plink_add(&other->fplp,cfp); - } - } - } -} - -/* Compute all followsets. -** -** A followset is the set of all symbols which can come immediately -** after a configuration. -*/ -void FindFollowSets(struct lemon *lemp) -{ - int i; - struct config *cfp; - struct plink *plp; - int progress; - int change; - - for(i=0; instate; i++){ - for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ - cfp->status = INCOMPLETE; - } - } - - do{ - progress = 0; - for(i=0; instate; i++){ - for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ - if( cfp->status==COMPLETE ) continue; - for(plp=cfp->fplp; plp; plp=plp->next){ - change = SetUnion(plp->cfp->fws,cfp->fws); - if( change ){ - plp->cfp->status = INCOMPLETE; - progress = 1; - } - } - cfp->status = COMPLETE; - } - } - }while( progress ); -} - -static int resolve_conflict(struct action *,struct action *); - -/* Compute the reduce actions, and resolve conflicts. -*/ -void FindActions(struct lemon *lemp) -{ - int i,j; - struct config *cfp; - struct state *stp; - struct symbol *sp; - struct rule *rp; - - /* Add all of the reduce actions - ** A reduce action is added for each element of the followset of - ** a configuration which has its dot at the extreme right. - */ - for(i=0; instate; i++){ /* Loop over all states */ - stp = lemp->sorted[i]; - for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */ - if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */ - for(j=0; jnterminal; j++){ - if( SetFind(cfp->fws,j) ){ - /* Add a reduce action to the state "stp" which will reduce by the - ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */ - Action_add(&stp->ap,REDUCE,lemp->symbols[j],(char *)cfp->rp); - } - } - } - } - } - - /* Add the accepting token */ - if( lemp->start ){ - sp = Symbol_find(lemp->start); - if( sp==0 ) sp = lemp->rule->lhs; - }else{ - sp = lemp->rule->lhs; - } - /* Add to the first state (which is always the starting state of the - ** finite state machine) an action to ACCEPT if the lookahead is the - ** start nonterminal. */ - Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0); - - /* Resolve conflicts */ - for(i=0; instate; i++){ - struct action *ap, *nap; - struct state *stp; - stp = lemp->sorted[i]; - /* assert( stp->ap ); */ - stp->ap = Action_sort(stp->ap); - for(ap=stp->ap; ap && ap->next; ap=ap->next){ - for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){ - /* The two actions "ap" and "nap" have the same lookahead. - ** Figure out which one should be used */ - lemp->nconflict += resolve_conflict(ap,nap); - } - } - } - - /* Report an error for each rule that can never be reduced. */ - for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = LEMON_FALSE; - for(i=0; instate; i++){ - struct action *ap; - for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){ - if( ap->type==REDUCE ) ap->x.rp->canReduce = LEMON_TRUE; - } - } - for(rp=lemp->rule; rp; rp=rp->next){ - if( rp->canReduce ) continue; - ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n"); - lemp->errorcnt++; - } -} - -/* Resolve a conflict between the two given actions. If the -** conflict can't be resolved, return non-zero. -** -** NO LONGER TRUE: -** To resolve a conflict, first look to see if either action -** is on an error rule. In that case, take the action which -** is not associated with the error rule. If neither or both -** actions are associated with an error rule, then try to -** use precedence to resolve the conflict. -** -** If either action is a SHIFT, then it must be apx. This -** function won't work if apx->type==REDUCE and apy->type==SHIFT. -*/ -static int resolve_conflict( - struct action *apx, - struct action *apy -){ - struct symbol *spx, *spy; - int errcnt = 0; - assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */ - if( apx->type==SHIFT && apy->type==SHIFT ){ - apy->type = SSCONFLICT; - errcnt++; - } - if( apx->type==SHIFT && apy->type==REDUCE ){ - spx = apx->sp; - spy = apy->x.rp->precsym; - if( spy==0 || spx->prec<0 || spy->prec<0 ){ - /* Not enough precedence information. */ - apy->type = SRCONFLICT; - errcnt++; - }else if( spx->prec>spy->prec ){ /* higher precedence wins */ - apy->type = RD_RESOLVED; - }else if( spx->precprec ){ - apx->type = SH_RESOLVED; - }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */ - apy->type = RD_RESOLVED; /* associativity */ - }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */ - apx->type = SH_RESOLVED; - }else{ - assert( spx->prec==spy->prec && spx->assoc==NONE ); - apx->type = ERROR; - } - }else if( apx->type==REDUCE && apy->type==REDUCE ){ - spx = apx->x.rp->precsym; - spy = apy->x.rp->precsym; - if( spx==0 || spy==0 || spx->prec<0 || - spy->prec<0 || spx->prec==spy->prec ){ - apy->type = RRCONFLICT; - errcnt++; - }else if( spx->prec>spy->prec ){ - apy->type = RD_RESOLVED; - }else if( spx->precprec ){ - apx->type = RD_RESOLVED; - } - }else{ - assert( - apx->type==SH_RESOLVED || - apx->type==RD_RESOLVED || - apx->type==SSCONFLICT || - apx->type==SRCONFLICT || - apx->type==RRCONFLICT || - apy->type==SH_RESOLVED || - apy->type==RD_RESOLVED || - apy->type==SSCONFLICT || - apy->type==SRCONFLICT || - apy->type==RRCONFLICT - ); - /* The REDUCE/SHIFT case cannot happen because SHIFTs come before - ** REDUCEs on the list. If we reach this point it must be because - ** the parser conflict had already been resolved. */ - } - return errcnt; -} -/********************* From the file "configlist.c" *************************/ -/* -** Routines to processing a configuration list and building a state -** in the LEMON parser generator. -*/ - -static struct config *freelist = 0; /* List of free configurations */ -static struct config *current = 0; /* Top of list of configurations */ -static struct config **currentend = 0; /* Last on list of configs */ -static struct config *basis = 0; /* Top of list of basis configs */ -static struct config **basisend = 0; /* End of list of basis configs */ - -/* Return a pointer to a new configuration */ -PRIVATE struct config *newconfig(){ - struct config *newcfg; - if( freelist==0 ){ - int i; - int amt = 3; - freelist = (struct config *)calloc( amt, sizeof(struct config) ); - if( freelist==0 ){ - fprintf(stderr,"Unable to allocate memory for a new configuration."); - exit(1); - } - for(i=0; inext; - return newcfg; -} - -/* The configuration "old" is no longer used */ -PRIVATE void deleteconfig(struct config *old) -{ - old->next = freelist; - freelist = old; -} - -/* Initialized the configuration list builder */ -void Configlist_init(){ - current = 0; - currentend = ¤t; - basis = 0; - basisend = &basis; - Configtable_init(); - return; -} - -/* Initialized the configuration list builder */ -void Configlist_reset(){ - current = 0; - currentend = ¤t; - basis = 0; - basisend = &basis; - Configtable_clear(0); - return; -} - -/* Add another configuration to the configuration list */ -struct config *Configlist_add( - struct rule *rp, /* The rule */ - int dot /* Index into the RHS of the rule where the dot goes */ -){ - struct config *cfp, model; - - assert( currentend!=0 ); - model.rp = rp; - model.dot = dot; - cfp = Configtable_find(&model); - if( cfp==0 ){ - cfp = newconfig(); - cfp->rp = rp; - cfp->dot = dot; - cfp->fws = SetNew(); - cfp->stp = 0; - cfp->fplp = cfp->bplp = 0; - cfp->next = 0; - cfp->bp = 0; - *currentend = cfp; - currentend = &cfp->next; - Configtable_insert(cfp); - } - return cfp; -} - -/* Add a basis configuration to the configuration list */ -struct config *Configlist_addbasis(struct rule *rp, int dot) -{ - struct config *cfp, model; - - assert( basisend!=0 ); - assert( currentend!=0 ); - model.rp = rp; - model.dot = dot; - cfp = Configtable_find(&model); - if( cfp==0 ){ - cfp = newconfig(); - cfp->rp = rp; - cfp->dot = dot; - cfp->fws = SetNew(); - cfp->stp = 0; - cfp->fplp = cfp->bplp = 0; - cfp->next = 0; - cfp->bp = 0; - *currentend = cfp; - currentend = &cfp->next; - *basisend = cfp; - basisend = &cfp->bp; - Configtable_insert(cfp); - } - return cfp; -} - -/* Compute the closure of the configuration list */ -void Configlist_closure(struct lemon *lemp) -{ - struct config *cfp, *newcfp; - struct rule *rp, *newrp; - struct symbol *sp, *xsp; - int i, dot; - - assert( currentend!=0 ); - for(cfp=current; cfp; cfp=cfp->next){ - rp = cfp->rp; - dot = cfp->dot; - if( dot>=rp->nrhs ) continue; - sp = rp->rhs[dot]; - if( sp->type==NONTERMINAL ){ - if( sp->rule==0 && sp!=lemp->errsym ){ - ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.", - sp->name); - lemp->errorcnt++; - } - for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){ - newcfp = Configlist_add(newrp,0); - for(i=dot+1; inrhs; i++){ - xsp = rp->rhs[i]; - if( xsp->type==TERMINAL ){ - SetAdd(newcfp->fws,xsp->index); - break; - }else if( xsp->type==MULTITERMINAL ){ - int k; - for(k=0; knsubsym; k++){ - SetAdd(newcfp->fws, xsp->subsym[k]->index); - } - break; - }else{ - SetUnion(newcfp->fws,xsp->firstset); - if( xsp->lambda==LEMON_FALSE ) break; - } - } - if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp); - } - } - } - return; -} - -/* Sort the configuration list */ -void Configlist_sort(){ - current = (struct config *)msort((char *)current,(char **)&(current->next),Configcmp); - currentend = 0; - return; -} - -/* Sort the basis configuration list */ -void Configlist_sortbasis(){ - basis = (struct config *)msort((char *)current,(char **)&(current->bp),Configcmp); - basisend = 0; - return; -} - -/* Return a pointer to the head of the configuration list and -** reset the list */ -struct config *Configlist_return(){ - struct config *old; - old = current; - current = 0; - currentend = 0; - return old; -} - -/* Return a pointer to the head of the configuration list and -** reset the list */ -struct config *Configlist_basis(){ - struct config *old; - old = basis; - basis = 0; - basisend = 0; - return old; -} - -/* Free all elements of the given configuration list */ -void Configlist_eat(struct config *cfp) -{ - struct config *nextcfp; - for(; cfp; cfp=nextcfp){ - nextcfp = cfp->next; - assert( cfp->fplp==0 ); - assert( cfp->bplp==0 ); - if( cfp->fws ) SetFree(cfp->fws); - deleteconfig(cfp); - } - return; -} -/***************** From the file "error.c" *********************************/ -/* -** Code for printing error message. -*/ - -void ErrorMsg(const char *filename, int lineno, const char *format, ...){ - va_list ap; - fprintf(stderr, "%s:%d: ", filename, lineno); - va_start(ap, format); - vfprintf(stderr,format,ap); - va_end(ap); - fprintf(stderr, "\n"); -} -/**************** From the file "main.c" ************************************/ -/* -** Main program file for the LEMON parser generator. -*/ - -/* Report an out-of-memory condition and abort. This function -** is used mostly by the "MemoryCheck" macro in struct.h -*/ -void memory_error(){ - fprintf(stderr,"Out of memory. Aborting...\n"); - exit(1); -} - -static int nDefine = 0; /* Number of -D options on the command line */ -static char **azDefine = 0; /* Name of the -D macros */ - -/* This routine is called with the argument to each -D command-line option. -** Add the macro defined to the azDefine array. -*/ -static void handle_D_option(char *z){ - char **paz; - nDefine++; - azDefine = (char **) realloc(azDefine, sizeof(azDefine[0])*nDefine); - if( azDefine==0 ){ - fprintf(stderr,"out of memory\n"); - exit(1); - } - paz = &azDefine[nDefine-1]; - *paz = (char *) malloc( lemonStrlen(z)+1 ); - if( *paz==0 ){ - fprintf(stderr,"out of memory\n"); - exit(1); - } - lemon_strcpy(*paz, z); - for(z=*paz; *z && *z!='='; z++){} - *z = 0; -} - -static char *user_templatename = NULL; -static void handle_T_option(char *z){ - user_templatename = (char *) malloc( lemonStrlen(z)+1 ); - if( user_templatename==0 ){ - memory_error(); - } - lemon_strcpy(user_templatename, z); -} - -/* The main program. Parse the command line and do it... */ -int main(int argc, char **argv) -{ - static int version = 0; - static int rpflag = 0; - static int basisflag = 0; - static int compress = 0; - static int quiet = 0; - static int statistics = 0; - static int mhflag = 0; - static int nolinenosflag = 0; - static int noResort = 0; - static struct s_options options[] = { - {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."}, - {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."}, - {OPT_FSTR, "D", (char*)handle_D_option, "Define an %ifdef macro."}, - {OPT_FSTR, "T", (char*)handle_T_option, "Specify a template file."}, - {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."}, - {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file."}, - {OPT_FLAG, "l", (char*)&nolinenosflag, "Do not print #line statements."}, - {OPT_FLAG, "p", (char*)&showPrecedenceConflict, - "Show conflicts resolved by precedence rules"}, - {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."}, - {OPT_FLAG, "r", (char*)&noResort, "Do not sort or renumber states"}, - {OPT_FLAG, "s", (char*)&statistics, - "Print parser stats to standard output."}, - {OPT_FLAG, "x", (char*)&version, "Print the version number."}, - {OPT_FLAG,0,0,0} - }; - int i; - int exitcode; - struct lemon lem; - - OptInit(argv,options,stderr); - if( version ){ - printf("Lemon version 1.0\n"); - exit(0); - } - if( OptNArgs()!=1 ){ - fprintf(stderr,"Exactly one filename argument is required.\n"); - exit(1); - } - memset(&lem, 0, sizeof(lem)); - lem.errorcnt = 0; - - /* Initialize the machine */ - Strsafe_init(); - Symbol_init(); - State_init(); - lem.argv0 = argv[0]; - lem.filename = OptArg(0); - lem.basisflag = basisflag; - lem.nolinenosflag = nolinenosflag; - Symbol_new("$"); - lem.errsym = Symbol_new("error"); - lem.errsym->useCnt = 0; - - /* Parse the input file */ - Parse(&lem); - if( lem.errorcnt ) exit(lem.errorcnt); - if( lem.nrule==0 ){ - fprintf(stderr,"Empty grammar.\n"); - exit(1); - } - - /* Count and index the symbols of the grammar */ - Symbol_new("{default}"); - lem.nsymbol = Symbol_count(); - lem.symbols = Symbol_arrayof(); - for(i=0; iindex = i; - qsort(lem.symbols,lem.nsymbol,sizeof(struct symbol*), Symbolcmpp); - for(i=0; iindex = i; - while( lem.symbols[i-1]->type==MULTITERMINAL ){ i--; } - assert( strcmp(lem.symbols[i-1]->name,"{default}")==0 ); - lem.nsymbol = i - 1; - for(i=1; isupper(lem.symbols[i]->name[0]); i++); - lem.nterminal = i; - - /* Generate a reprint of the grammar, if requested on the command line */ - if( rpflag ){ - Reprint(&lem); - }else{ - /* Initialize the size for all follow and first sets */ - SetSize(lem.nterminal+1); - - /* Find the precedence for every production rule (that has one) */ - FindRulePrecedences(&lem); - - /* Compute the lambda-nonterminals and the first-sets for every - ** nonterminal */ - FindFirstSets(&lem); - - /* Compute all LR(0) states. Also record follow-set propagation - ** links so that the follow-set can be computed later */ - lem.nstate = 0; - FindStates(&lem); - lem.sorted = State_arrayof(); - - /* Tie up loose ends on the propagation links */ - FindLinks(&lem); - - /* Compute the follow set of every reducible configuration */ - FindFollowSets(&lem); - - /* Compute the action tables */ - FindActions(&lem); - - /* Compress the action tables */ - if( compress==0 ) CompressTables(&lem); - - /* Reorder and renumber the states so that states with fewer choices - ** occur at the end. This is an optimization that helps make the - ** generated parser tables smaller. */ - if( noResort==0 ) ResortStates(&lem); - - /* Generate a report of the parser generated. (the "y.output" file) */ - if( !quiet ) ReportOutput(&lem); - - /* Generate the source code for the parser */ - ReportTable(&lem, mhflag); - - /* Produce a header file for use by the scanner. (This step is - ** omitted if the "-m" option is used because makeheaders will - ** generate the file for us.) */ - if( !mhflag ) ReportHeader(&lem); - } - if( statistics ){ - printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n", - lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule); - printf(" %d states, %d parser table entries, %d conflicts\n", - lem.nstate, lem.tablesize, lem.nconflict); - } - if( lem.nconflict > 0 ){ - fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict); - } - - /* return 0 on success, 1 on failure. */ - exitcode = ((lem.errorcnt > 0) || (lem.nconflict > 0)) ? 1 : 0; - exit(exitcode); - return (exitcode); -} -/******************** From the file "msort.c" *******************************/ -/* -** A generic merge-sort program. -** -** USAGE: -** Let "ptr" be a pointer to some structure which is at the head of -** a null-terminated list. Then to sort the list call: -** -** ptr = msort(ptr,&(ptr->next),cmpfnc); -** -** In the above, "cmpfnc" is a pointer to a function which compares -** two instances of the structure and returns an integer, as in -** strcmp. The second argument is a pointer to the pointer to the -** second element of the linked list. This address is used to compute -** the offset to the "next" field within the structure. The offset to -** the "next" field must be constant for all structures in the list. -** -** The function returns a new pointer which is the head of the list -** after sorting. -** -** ALGORITHM: -** Merge-sort. -*/ - -/* -** Return a pointer to the next structure in the linked list. -*/ -#define NEXT(A) (*(char**)(((char*)A)+offset)) - -/* -** Inputs: -** a: A sorted, null-terminated linked list. (May be null). -** b: A sorted, null-terminated linked list. (May be null). -** cmp: A pointer to the comparison function. -** offset: Offset in the structure to the "next" field. -** -** Return Value: -** A pointer to the head of a sorted list containing the elements -** of both a and b. -** -** Side effects: -** The "next" pointers for elements in the lists a and b are -** changed. -*/ -static char *merge( - char *a, - char *b, - int (*cmp)(const char*,const char*), - int offset -){ - char *ptr, *head; - - if( a==0 ){ - head = b; - }else if( b==0 ){ - head = a; - }else{ - if( (*cmp)(a,b)<=0 ){ - ptr = a; - a = NEXT(a); - }else{ - ptr = b; - b = NEXT(b); - } - head = ptr; - while( a && b ){ - if( (*cmp)(a,b)<=0 ){ - NEXT(ptr) = a; - ptr = a; - a = NEXT(a); - }else{ - NEXT(ptr) = b; - ptr = b; - b = NEXT(b); - } - } - if( a ) NEXT(ptr) = a; - else NEXT(ptr) = b; - } - return head; -} - -/* -** Inputs: -** list: Pointer to a singly-linked list of structures. -** next: Pointer to pointer to the second element of the list. -** cmp: A comparison function. -** -** Return Value: -** A pointer to the head of a sorted list containing the elements -** orginally in list. -** -** Side effects: -** The "next" pointers for elements in list are changed. -*/ -#define LISTSIZE 30 -static char *msort( - char *list, - char **next, - int (*cmp)(const char*,const char*) -){ - unsigned long offset; - char *ep; - char *set[LISTSIZE]; - int i; - offset = (unsigned long)next - (unsigned long)list; - for(i=0; istate = WAITING_FOR_DECL_KEYWORD; - }else if( islower(x[0]) ){ - psp->lhs = Symbol_new(x); - psp->nrhs = 0; - psp->lhsalias = 0; - psp->state = WAITING_FOR_ARROW; - }else if( x[0]=='{' ){ - if( psp->prevrule==0 ){ - ErrorMsg(psp->filename,psp->tokenlineno, -"There is no prior rule upon which to attach the code \ -fragment which begins on this line."); - psp->errorcnt++; - }else if( psp->prevrule->code!=0 ){ - ErrorMsg(psp->filename,psp->tokenlineno, -"Code fragment beginning on this line is not the first \ -to follow the previous rule."); - psp->errorcnt++; - }else{ - psp->prevrule->line = psp->tokenlineno; - psp->prevrule->code = &x[1]; - } - }else if( x[0]=='[' ){ - psp->state = PRECEDENCE_MARK_1; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Token \"%s\" should be either \"%%\" or a nonterminal name.", - x); - psp->errorcnt++; - } - break; - case PRECEDENCE_MARK_1: - if( !isupper(x[0]) ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "The precedence symbol must be a terminal."); - psp->errorcnt++; - }else if( psp->prevrule==0 ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "There is no prior rule to assign precedence \"[%s]\".",x); - psp->errorcnt++; - }else if( psp->prevrule->precsym!=0 ){ - ErrorMsg(psp->filename,psp->tokenlineno, -"Precedence mark on this line is not the first \ -to follow the previous rule."); - psp->errorcnt++; - }else{ - psp->prevrule->precsym = Symbol_new(x); - } - psp->state = PRECEDENCE_MARK_2; - break; - case PRECEDENCE_MARK_2: - if( x[0]!=']' ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "Missing \"]\" on precedence mark."); - psp->errorcnt++; - } - psp->state = WAITING_FOR_DECL_OR_RULE; - break; - case WAITING_FOR_ARROW: - if( x[0]==':' && x[1]==':' && x[2]=='=' ){ - psp->state = IN_RHS; - }else if( x[0]=='(' ){ - psp->state = LHS_ALIAS_1; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Expected to see a \":\" following the LHS symbol \"%s\".", - psp->lhs->name); - psp->errorcnt++; - psp->state = RESYNC_AFTER_RULE_ERROR; - } - break; - case LHS_ALIAS_1: - if( isalpha(x[0]) ){ - psp->lhsalias = x; - psp->state = LHS_ALIAS_2; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "\"%s\" is not a valid alias for the LHS \"%s\"\n", - x,psp->lhs->name); - psp->errorcnt++; - psp->state = RESYNC_AFTER_RULE_ERROR; - } - break; - case LHS_ALIAS_2: - if( x[0]==')' ){ - psp->state = LHS_ALIAS_3; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); - psp->errorcnt++; - psp->state = RESYNC_AFTER_RULE_ERROR; - } - break; - case LHS_ALIAS_3: - if( x[0]==':' && x[1]==':' && x[2]=='=' ){ - psp->state = IN_RHS; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Missing \"->\" following: \"%s(%s)\".", - psp->lhs->name,psp->lhsalias); - psp->errorcnt++; - psp->state = RESYNC_AFTER_RULE_ERROR; - } - break; - case IN_RHS: - if( x[0]=='.' ){ - struct rule *rp; - rp = (struct rule *)calloc( sizeof(struct rule) + - sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs, 1); - if( rp==0 ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "Can't allocate enough memory for this rule."); - psp->errorcnt++; - psp->prevrule = 0; - }else{ - int i; - rp->ruleline = psp->tokenlineno; - rp->rhs = (struct symbol**)&rp[1]; - rp->rhsalias = (const char**)&(rp->rhs[psp->nrhs]); - for(i=0; inrhs; i++){ - rp->rhs[i] = psp->rhs[i]; - rp->rhsalias[i] = psp->alias[i]; - } - rp->lhs = psp->lhs; - rp->lhsalias = psp->lhsalias; - rp->nrhs = psp->nrhs; - rp->code = 0; - rp->precsym = 0; - rp->index = psp->gp->nrule++; - rp->nextlhs = rp->lhs->rule; - rp->lhs->rule = rp; - rp->next = 0; - if( psp->firstrule==0 ){ - psp->firstrule = psp->lastrule = rp; - }else{ - psp->lastrule->next = rp; - psp->lastrule = rp; - } - psp->prevrule = rp; - } - psp->state = WAITING_FOR_DECL_OR_RULE; - }else if( isalpha(x[0]) ){ - if( psp->nrhs>=MAXRHS ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "Too many symbols on RHS of rule beginning at \"%s\".", - x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_RULE_ERROR; - }else{ - psp->rhs[psp->nrhs] = Symbol_new(x); - psp->alias[psp->nrhs] = 0; - psp->nrhs++; - } - }else if( (x[0]=='|' || x[0]=='/') && psp->nrhs>0 ){ - struct symbol *msp = psp->rhs[psp->nrhs-1]; - if( msp->type!=MULTITERMINAL ){ - struct symbol *origsp = msp; - msp = (struct symbol *) calloc(1,sizeof(*msp)); - memset(msp, 0, sizeof(*msp)); - msp->type = MULTITERMINAL; - msp->nsubsym = 1; - msp->subsym = (struct symbol **) calloc(1,sizeof(struct symbol*)); - msp->subsym[0] = origsp; - msp->name = origsp->name; - psp->rhs[psp->nrhs-1] = msp; - } - msp->nsubsym++; - msp->subsym = (struct symbol **) realloc(msp->subsym, - sizeof(struct symbol*)*msp->nsubsym); - msp->subsym[msp->nsubsym-1] = Symbol_new(&x[1]); - if( islower(x[1]) || islower(msp->subsym[0]->name[0]) ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "Cannot form a compound containing a non-terminal"); - psp->errorcnt++; - } - }else if( x[0]=='(' && psp->nrhs>0 ){ - psp->state = RHS_ALIAS_1; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Illegal character on RHS of rule: \"%s\".",x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_RULE_ERROR; - } - break; - case RHS_ALIAS_1: - if( isalpha(x[0]) ){ - psp->alias[psp->nrhs-1] = x; - psp->state = RHS_ALIAS_2; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n", - x,psp->rhs[psp->nrhs-1]->name); - psp->errorcnt++; - psp->state = RESYNC_AFTER_RULE_ERROR; - } - break; - case RHS_ALIAS_2: - if( x[0]==')' ){ - psp->state = IN_RHS; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); - psp->errorcnt++; - psp->state = RESYNC_AFTER_RULE_ERROR; - } - break; - case WAITING_FOR_DECL_KEYWORD: - if( isalpha(x[0]) ){ - psp->declkeyword = x; - psp->declargslot = 0; - psp->decllinenoslot = 0; - psp->insertLineMacro = 1; - psp->state = WAITING_FOR_DECL_ARG; - if( strcmp(x,"name")==0 ){ - psp->declargslot = &(psp->gp->name); - psp->insertLineMacro = 0; - }else if( strcmp(x,"include")==0 ){ - psp->declargslot = &(psp->gp->include); - }else if( strcmp(x,"code")==0 ){ - psp->declargslot = &(psp->gp->extracode); - }else if( strcmp(x,"token_destructor")==0 ){ - psp->declargslot = &psp->gp->tokendest; - }else if( strcmp(x,"default_destructor")==0 ){ - psp->declargslot = &psp->gp->vardest; - }else if( strcmp(x,"token_prefix")==0 ){ - psp->declargslot = &psp->gp->tokenprefix; - psp->insertLineMacro = 0; - }else if( strcmp(x,"syntax_error")==0 ){ - psp->declargslot = &(psp->gp->error); - }else if( strcmp(x,"parse_accept")==0 ){ - psp->declargslot = &(psp->gp->accept); - }else if( strcmp(x,"parse_failure")==0 ){ - psp->declargslot = &(psp->gp->failure); - }else if( strcmp(x,"stack_overflow")==0 ){ - psp->declargslot = &(psp->gp->overflow); - }else if( strcmp(x,"extra_argument")==0 ){ - psp->declargslot = &(psp->gp->arg); - psp->insertLineMacro = 0; - }else if( strcmp(x,"token_type")==0 ){ - psp->declargslot = &(psp->gp->tokentype); - psp->insertLineMacro = 0; - }else if( strcmp(x,"default_type")==0 ){ - psp->declargslot = &(psp->gp->vartype); - psp->insertLineMacro = 0; - }else if( strcmp(x,"stack_size")==0 ){ - psp->declargslot = &(psp->gp->stacksize); - psp->insertLineMacro = 0; - }else if( strcmp(x,"start_symbol")==0 ){ - psp->declargslot = &(psp->gp->start); - psp->insertLineMacro = 0; - }else if( strcmp(x,"left")==0 ){ - psp->preccounter++; - psp->declassoc = LEFT; - psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; - }else if( strcmp(x,"right")==0 ){ - psp->preccounter++; - psp->declassoc = RIGHT; - psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; - }else if( strcmp(x,"nonassoc")==0 ){ - psp->preccounter++; - psp->declassoc = NONE; - psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; - }else if( strcmp(x,"destructor")==0 ){ - psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL; - }else if( strcmp(x,"type")==0 ){ - psp->state = WAITING_FOR_DATATYPE_SYMBOL; - }else if( strcmp(x,"fallback")==0 ){ - psp->fallback = 0; - psp->state = WAITING_FOR_FALLBACK_ID; - }else if( strcmp(x,"wildcard")==0 ){ - psp->state = WAITING_FOR_WILDCARD_ID; - }else if( strcmp(x,"token_class")==0 ){ - psp->state = WAITING_FOR_CLASS_ID; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Unknown declaration keyword: \"%%%s\".",x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - } - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Illegal declaration keyword: \"%s\".",x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - } - break; - case WAITING_FOR_DESTRUCTOR_SYMBOL: - if( !isalpha(x[0]) ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "Symbol name missing after %%destructor keyword"); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - }else{ - struct symbol *sp = Symbol_new(x); - psp->declargslot = &sp->destructor; - psp->decllinenoslot = &sp->destLineno; - psp->insertLineMacro = 1; - psp->state = WAITING_FOR_DECL_ARG; - } - break; - case WAITING_FOR_DATATYPE_SYMBOL: - if( !isalpha(x[0]) ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "Symbol name missing after %%type keyword"); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - }else{ - struct symbol *sp = Symbol_find(x); - if((sp) && (sp->datatype)){ - ErrorMsg(psp->filename,psp->tokenlineno, - "Symbol %%type \"%s\" already defined", x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - }else{ - if (!sp){ - sp = Symbol_new(x); - } - psp->declargslot = &sp->datatype; - psp->insertLineMacro = 0; - psp->state = WAITING_FOR_DECL_ARG; - } - } - break; - case WAITING_FOR_PRECEDENCE_SYMBOL: - if( x[0]=='.' ){ - psp->state = WAITING_FOR_DECL_OR_RULE; - }else if( isupper(x[0]) ){ - struct symbol *sp; - sp = Symbol_new(x); - if( sp->prec>=0 ){ - ErrorMsg(psp->filename,psp->tokenlineno, - "Symbol \"%s\" has already be given a precedence.",x); - psp->errorcnt++; - }else{ - sp->prec = psp->preccounter; - sp->assoc = psp->declassoc; - } - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Can't assign a precedence to \"%s\".",x); - psp->errorcnt++; - } - break; - case WAITING_FOR_DECL_ARG: - if( x[0]=='{' || x[0]=='\"' || isalnum(x[0]) ){ - const char *zOld, *zNew; - char *zBuf, *z; - int nOld, n, nLine, nNew, nBack; - int addLineMacro; - char zLine[50]; - zNew = x; - if( zNew[0]=='"' || zNew[0]=='{' ) zNew++; - nNew = lemonStrlen(zNew); - if( *psp->declargslot ){ - zOld = *psp->declargslot; - }else{ - zOld = ""; - } - nOld = lemonStrlen(zOld); - n = nOld + nNew + 20; - addLineMacro = !psp->gp->nolinenosflag && psp->insertLineMacro && - (psp->decllinenoslot==0 || psp->decllinenoslot[0]!=0); - if( addLineMacro ){ - for(z=psp->filename, nBack=0; *z; z++){ - if( *z=='\\' ) nBack++; - } - lemon_sprintf(zLine, "#line %d ", psp->tokenlineno); - nLine = lemonStrlen(zLine); - n += nLine + lemonStrlen(psp->filename) + nBack; - } - *psp->declargslot = (char *) realloc(*psp->declargslot, n); - zBuf = *psp->declargslot + nOld; - if( addLineMacro ){ - if( nOld && zBuf[-1]!='\n' ){ - *(zBuf++) = '\n'; - } - memcpy(zBuf, zLine, nLine); - zBuf += nLine; - *(zBuf++) = '"'; - for(z=psp->filename; *z; z++){ - if( *z=='\\' ){ - *(zBuf++) = '\\'; - } - *(zBuf++) = *z; - } - *(zBuf++) = '"'; - *(zBuf++) = '\n'; - } - if( psp->decllinenoslot && psp->decllinenoslot[0]==0 ){ - psp->decllinenoslot[0] = psp->tokenlineno; - } - memcpy(zBuf, zNew, nNew); - zBuf += nNew; - *zBuf = 0; - psp->state = WAITING_FOR_DECL_OR_RULE; - }else{ - ErrorMsg(psp->filename,psp->tokenlineno, - "Illegal argument to %%%s: %s",psp->declkeyword,x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - } - break; - case WAITING_FOR_FALLBACK_ID: - if( x[0]=='.' ){ - psp->state = WAITING_FOR_DECL_OR_RULE; - }else if( !isupper(x[0]) ){ - ErrorMsg(psp->filename, psp->tokenlineno, - "%%fallback argument \"%s\" should be a token", x); - psp->errorcnt++; - }else{ - struct symbol *sp = Symbol_new(x); - if( psp->fallback==0 ){ - psp->fallback = sp; - }else if( sp->fallback ){ - ErrorMsg(psp->filename, psp->tokenlineno, - "More than one fallback assigned to token %s", x); - psp->errorcnt++; - }else{ - sp->fallback = psp->fallback; - psp->gp->has_fallback = 1; - } - } - break; - case WAITING_FOR_WILDCARD_ID: - if( x[0]=='.' ){ - psp->state = WAITING_FOR_DECL_OR_RULE; - }else if( !isupper(x[0]) ){ - ErrorMsg(psp->filename, psp->tokenlineno, - "%%wildcard argument \"%s\" should be a token", x); - psp->errorcnt++; - }else{ - struct symbol *sp = Symbol_new(x); - if( psp->gp->wildcard==0 ){ - psp->gp->wildcard = sp; - }else{ - ErrorMsg(psp->filename, psp->tokenlineno, - "Extra wildcard to token: %s", x); - psp->errorcnt++; - } - } - break; - case WAITING_FOR_CLASS_ID: - if( !islower(x[0]) ){ - ErrorMsg(psp->filename, psp->tokenlineno, - "%%token_class must be followed by an identifier: ", x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - }else if( Symbol_find(x) ){ - ErrorMsg(psp->filename, psp->tokenlineno, - "Symbol \"%s\" already used", x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - }else{ - psp->tkclass = Symbol_new(x); - psp->tkclass->type = MULTITERMINAL; - psp->state = WAITING_FOR_CLASS_TOKEN; - } - break; - case WAITING_FOR_CLASS_TOKEN: - if( x[0]=='.' ){ - psp->state = WAITING_FOR_DECL_OR_RULE; - }else if( isupper(x[0]) || ((x[0]=='|' || x[0]=='/') && isupper(x[1])) ){ - struct symbol *msp = psp->tkclass; - msp->nsubsym++; - msp->subsym = (struct symbol **) realloc(msp->subsym, - sizeof(struct symbol*)*msp->nsubsym); - if( !isupper(x[0]) ) x++; - msp->subsym[msp->nsubsym-1] = Symbol_new(x); - }else{ - ErrorMsg(psp->filename, psp->tokenlineno, - "%%token_class argument \"%s\" should be a token", x); - psp->errorcnt++; - psp->state = RESYNC_AFTER_DECL_ERROR; - } - break; - case RESYNC_AFTER_RULE_ERROR: -/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; -** break; */ - case RESYNC_AFTER_DECL_ERROR: - if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; - if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD; - break; - } -} - -/* Run the preprocessor over the input file text. The global variables -** azDefine[0] through azDefine[nDefine-1] contains the names of all defined -** macros. This routine looks for "%ifdef" and "%ifndef" and "%endif" and -** comments them out. Text in between is also commented out as appropriate. -*/ -static void preprocess_input(char *z){ - int i, j, k, n; - int exclude = 0; - int start = 0; - int lineno = 1; - int start_lineno = 1; - for(i=0; z[i]; i++){ - if( z[i]=='\n' ) lineno++; - if( z[i]!='%' || (i>0 && z[i-1]!='\n') ) continue; - if( strncmp(&z[i],"%endif",6)==0 && isspace(z[i+6]) ){ - if( exclude ){ - exclude--; - if( exclude==0 ){ - for(j=start; jfilename; - ps.errorcnt = 0; - ps.state = INITIALIZE; - - /* Begin by reading the input file */ - fp = fopen(ps.filename,"rb"); - if( fp==0 ){ - ErrorMsg(ps.filename,0,"Can't open this file for reading."); - gp->errorcnt++; - return; - } - fseek(fp,0,2); - filesize = ftell(fp); - rewind(fp); - filebuf = (char *)malloc( filesize+1 ); - if( filesize>100000000 || filebuf==0 ){ - ErrorMsg(ps.filename,0,"Input file too large."); - gp->errorcnt++; - fclose(fp); - return; - } - if( fread(filebuf,1,filesize,fp)!=filesize ){ - ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.", - filesize); - free(filebuf); - gp->errorcnt++; - fclose(fp); - return; - } - fclose(fp); - filebuf[filesize] = 0; - - /* Make an initial pass through the file to handle %ifdef and %ifndef */ - preprocess_input(filebuf); - - /* Now scan the text of the input file */ - lineno = 1; - for(cp=filebuf; (c= *cp)!=0; ){ - if( c=='\n' ) lineno++; /* Keep track of the line number */ - if( isspace(c) ){ cp++; continue; } /* Skip all white space */ - if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */ - cp+=2; - while( (c= *cp)!=0 && c!='\n' ) cp++; - continue; - } - if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */ - cp+=2; - while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){ - if( c=='\n' ) lineno++; - cp++; - } - if( c ) cp++; - continue; - } - ps.tokenstart = cp; /* Mark the beginning of the token */ - ps.tokenlineno = lineno; /* Linenumber on which token begins */ - if( c=='\"' ){ /* String literals */ - cp++; - while( (c= *cp)!=0 && c!='\"' ){ - if( c=='\n' ) lineno++; - cp++; - } - if( c==0 ){ - ErrorMsg(ps.filename,startline, -"String starting on this line is not terminated before the end of the file."); - ps.errorcnt++; - nextcp = cp; - }else{ - nextcp = cp+1; - } - }else if( c=='{' ){ /* A block of C code */ - int level; - cp++; - for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){ - if( c=='\n' ) lineno++; - else if( c=='{' ) level++; - else if( c=='}' ) level--; - else if( c=='/' && cp[1]=='*' ){ /* Skip comments */ - int prevc; - cp = &cp[2]; - prevc = 0; - while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){ - if( c=='\n' ) lineno++; - prevc = c; - cp++; - } - }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */ - cp = &cp[2]; - while( (c= *cp)!=0 && c!='\n' ) cp++; - if( c ) lineno++; - }else if( c=='\'' || c=='\"' ){ /* String a character literals */ - int startchar, prevc; - startchar = c; - prevc = 0; - for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){ - if( c=='\n' ) lineno++; - if( prevc=='\\' ) prevc = 0; - else prevc = c; - } - } - } - if( c==0 ){ - ErrorMsg(ps.filename,ps.tokenlineno, -"C code starting on this line is not terminated before the end of the file."); - ps.errorcnt++; - nextcp = cp; - }else{ - nextcp = cp+1; - } - }else if( isalnum(c) ){ /* Identifiers */ - while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++; - nextcp = cp; - }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */ - cp += 3; - nextcp = cp; - }else if( (c=='/' || c=='|') && isalpha(cp[1]) ){ - cp += 2; - while( (c = *cp)!=0 && (isalnum(c) || c=='_') ) cp++; - nextcp = cp; - }else{ /* All other (one character) operators */ - cp++; - nextcp = cp; - } - c = *cp; - *cp = 0; /* Null terminate the token */ - parseonetoken(&ps); /* Parse the token */ - *cp = c; /* Restore the buffer */ - cp = nextcp; - } - free(filebuf); /* Release the buffer after parsing */ - gp->rule = ps.firstrule; - gp->errorcnt = ps.errorcnt; -} -/*************************** From the file "plink.c" *********************/ -/* -** Routines processing configuration follow-set propagation links -** in the LEMON parser generator. -*/ -static struct plink *plink_freelist = 0; - -/* Allocate a new plink */ -struct plink *Plink_new(){ - struct plink *newlink; - - if( plink_freelist==0 ){ - int i; - int amt = 100; - plink_freelist = (struct plink *)calloc( amt, sizeof(struct plink) ); - if( plink_freelist==0 ){ - fprintf(stderr, - "Unable to allocate memory for a new follow-set propagation link.\n"); - exit(1); - } - for(i=0; inext; - return newlink; -} - -/* Add a plink to a plink list */ -void Plink_add(struct plink **plpp, struct config *cfp) -{ - struct plink *newlink; - newlink = Plink_new(); - newlink->next = *plpp; - *plpp = newlink; - newlink->cfp = cfp; -} - -/* Transfer every plink on the list "from" to the list "to" */ -void Plink_copy(struct plink **to, struct plink *from) -{ - struct plink *nextpl; - while( from ){ - nextpl = from->next; - from->next = *to; - *to = from; - from = nextpl; - } -} - -/* Delete every plink on the list */ -void Plink_delete(struct plink *plp) -{ - struct plink *nextpl; - - while( plp ){ - nextpl = plp->next; - plp->next = plink_freelist; - plink_freelist = plp; - plp = nextpl; - } -} -/*********************** From the file "report.c" **************************/ -/* -** Procedures for generating reports and tables in the LEMON parser generator. -*/ - -/* Generate a filename with the given suffix. Space to hold the -** name comes from malloc() and must be freed by the calling -** function. -*/ -PRIVATE char *file_makename(struct lemon *lemp, const char *suffix) -{ - char *name; - char *cp; - - name = (char*)malloc( lemonStrlen(lemp->filename) + lemonStrlen(suffix) + 5 ); - if( name==0 ){ - fprintf(stderr,"Can't allocate space for a filename.\n"); - exit(1); - } - lemon_strcpy(name,lemp->filename); - cp = strrchr(name,'.'); - if( cp ) *cp = 0; - lemon_strcat(name,suffix); - return name; -} - -/* Open a file with a name based on the name of the input file, -** but with a different (specified) suffix, and return a pointer -** to the stream */ -PRIVATE FILE *file_open( - struct lemon *lemp, - const char *suffix, - const char *mode -){ - FILE *fp; - - if( lemp->outname ) free(lemp->outname); - lemp->outname = file_makename(lemp, suffix); - fp = fopen(lemp->outname,mode); - if( fp==0 && *mode=='w' ){ - fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname); - lemp->errorcnt++; - return 0; - } - return fp; -} - -/* Duplicate the input file without comments and without actions -** on rules */ -void Reprint(struct lemon *lemp) -{ - struct rule *rp; - struct symbol *sp; - int i, j, maxlen, len, ncolumns, skip; - printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename); - maxlen = 10; - for(i=0; insymbol; i++){ - sp = lemp->symbols[i]; - len = lemonStrlen(sp->name); - if( len>maxlen ) maxlen = len; - } - ncolumns = 76/(maxlen+5); - if( ncolumns<1 ) ncolumns = 1; - skip = (lemp->nsymbol + ncolumns - 1)/ncolumns; - for(i=0; insymbol; j+=skip){ - sp = lemp->symbols[j]; - assert( sp->index==j ); - printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name); - } - printf("\n"); - } - for(rp=lemp->rule; rp; rp=rp->next){ - printf("%s",rp->lhs->name); - /* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */ - printf(" ::="); - for(i=0; inrhs; i++){ - sp = rp->rhs[i]; - if( sp->type==MULTITERMINAL ){ - printf(" %s", sp->subsym[0]->name); - for(j=1; jnsubsym; j++){ - printf("|%s", sp->subsym[j]->name); - } - }else{ - printf(" %s", sp->name); - } - /* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */ - } - printf("."); - if( rp->precsym ) printf(" [%s]",rp->precsym->name); - /* if( rp->code ) printf("\n %s",rp->code); */ - printf("\n"); - } -} - -void ConfigPrint(FILE *fp, struct config *cfp) -{ - struct rule *rp; - struct symbol *sp; - int i, j; - rp = cfp->rp; - fprintf(fp,"%s ::=",rp->lhs->name); - for(i=0; i<=rp->nrhs; i++){ - if( i==cfp->dot ) fprintf(fp," *"); - if( i==rp->nrhs ) break; - sp = rp->rhs[i]; - if( sp->type==MULTITERMINAL ){ - fprintf(fp," %s", sp->subsym[0]->name); - for(j=1; jnsubsym; j++){ - fprintf(fp,"|%s",sp->subsym[j]->name); - } - }else{ - fprintf(fp," %s", sp->name); - } - } -} - -/* #define TEST */ -#if 0 -/* Print a set */ -PRIVATE void SetPrint(out,set,lemp) -FILE *out; -char *set; -struct lemon *lemp; -{ - int i; - char *spacer; - spacer = ""; - fprintf(out,"%12s[",""); - for(i=0; interminal; i++){ - if( SetFind(set,i) ){ - fprintf(out,"%s%s",spacer,lemp->symbols[i]->name); - spacer = " "; - } - } - fprintf(out,"]\n"); -} - -/* Print a plink chain */ -PRIVATE void PlinkPrint(out,plp,tag) -FILE *out; -struct plink *plp; -char *tag; -{ - while( plp ){ - fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->statenum); - ConfigPrint(out,plp->cfp); - fprintf(out,"\n"); - plp = plp->next; - } -} -#endif - -/* Print an action to the given file descriptor. Return FALSE if -** nothing was actually printed. -*/ -int PrintAction(struct action *ap, FILE *fp, int indent){ - int result = 1; - switch( ap->type ){ - case SHIFT: - fprintf(fp,"%*s shift %d",indent,ap->sp->name,ap->x.stp->statenum); - break; - case REDUCE: - fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index); - break; - case ACCEPT: - fprintf(fp,"%*s accept",indent,ap->sp->name); - break; - case ERROR: - fprintf(fp,"%*s error",indent,ap->sp->name); - break; - case SRCONFLICT: - case RRCONFLICT: - fprintf(fp,"%*s reduce %-3d ** Parsing conflict **", - indent,ap->sp->name,ap->x.rp->index); - break; - case SSCONFLICT: - fprintf(fp,"%*s shift %-3d ** Parsing conflict **", - indent,ap->sp->name,ap->x.stp->statenum); - break; - case SH_RESOLVED: - if( showPrecedenceConflict ){ - fprintf(fp,"%*s shift %-3d -- dropped by precedence", - indent,ap->sp->name,ap->x.stp->statenum); - }else{ - result = 0; - } - break; - case RD_RESOLVED: - if( showPrecedenceConflict ){ - fprintf(fp,"%*s reduce %-3d -- dropped by precedence", - indent,ap->sp->name,ap->x.rp->index); - }else{ - result = 0; - } - break; - case NOT_USED: - result = 0; - break; - } - return result; -} - -/* Generate the "y.output" log file */ -void ReportOutput(struct lemon *lemp) -{ - int i; - struct state *stp; - struct config *cfp; - struct action *ap; - FILE *fp; - - fp = file_open(lemp,".out","wb"); - if( fp==0 ) return; - for(i=0; instate; i++){ - stp = lemp->sorted[i]; - fprintf(fp,"State %d:\n",stp->statenum); - if( lemp->basisflag ) cfp=stp->bp; - else cfp=stp->cfp; - while( cfp ){ - char buf[20]; - if( cfp->dot==cfp->rp->nrhs ){ - lemon_sprintf(buf,"(%d)",cfp->rp->index); - fprintf(fp," %5s ",buf); - }else{ - fprintf(fp," "); - } - ConfigPrint(fp,cfp); - fprintf(fp,"\n"); -#if 0 - SetPrint(fp,cfp->fws,lemp); - PlinkPrint(fp,cfp->fplp,"To "); - PlinkPrint(fp,cfp->bplp,"From"); -#endif - if( lemp->basisflag ) cfp=cfp->bp; - else cfp=cfp->next; - } - fprintf(fp,"\n"); - for(ap=stp->ap; ap; ap=ap->next){ - if( PrintAction(ap,fp,30) ) fprintf(fp,"\n"); - } - fprintf(fp,"\n"); - } - fprintf(fp, "----------------------------------------------------\n"); - fprintf(fp, "Symbols:\n"); - for(i=0; insymbol; i++){ - int j; - struct symbol *sp; - - sp = lemp->symbols[i]; - fprintf(fp, " %3d: %s", i, sp->name); - if( sp->type==NONTERMINAL ){ - fprintf(fp, ":"); - if( sp->lambda ){ - fprintf(fp, " "); - } - for(j=0; jnterminal; j++){ - if( sp->firstset && SetFind(sp->firstset, j) ){ - fprintf(fp, " %s", lemp->symbols[j]->name); - } - } - } - fprintf(fp, "\n"); - } - fclose(fp); - return; -} - -/* Search for the file "name" which is in the same directory as -** the exacutable */ -PRIVATE char *pathsearch(char *argv0, char *name, int modemask) -{ - const char *pathlist; - char *pathbufptr; - char *pathbuf; - char *path,*cp; - char c; - -#ifdef __WIN32__ - cp = strrchr(argv0,'\\'); -#else - cp = strrchr(argv0,'/'); -#endif - if( cp ){ - c = *cp; - *cp = 0; - path = (char *)malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 ); - if( path ) lemon_sprintf(path,"%s/%s",argv0,name); - *cp = c; - }else{ - pathlist = getenv("PATH"); - if( pathlist==0 ) pathlist = ".:/bin:/usr/bin"; - pathbuf = (char *) malloc( lemonStrlen(pathlist) + 1 ); - path = (char *)malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 ); - if( (pathbuf != 0) && (path!=0) ){ - pathbufptr = pathbuf; - lemon_strcpy(pathbuf, pathlist); - while( *pathbuf ){ - cp = strchr(pathbuf,':'); - if( cp==0 ) cp = &pathbuf[lemonStrlen(pathbuf)]; - c = *cp; - *cp = 0; - lemon_sprintf(path,"%s/%s",pathbuf,name); - *cp = c; - if( c==0 ) pathbuf[0] = 0; - else pathbuf = &cp[1]; - if( access(path,modemask)==0 ) break; - } - free(pathbufptr); - } - } - return path; -} - -/* Given an action, compute the integer value for that action -** which is to be put in the action table of the generated machine. -** Return negative if no action should be generated. -*/ -PRIVATE int compute_action(struct lemon *lemp, struct action *ap) -{ - int act; - switch( ap->type ){ - case SHIFT: act = ap->x.stp->statenum; break; - case REDUCE: act = ap->x.rp->index + lemp->nstate; break; - case ERROR: act = lemp->nstate + lemp->nrule; break; - case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break; - default: act = -1; break; - } - return act; -} - -#define LINESIZE 1000 -/* The next cluster of routines are for reading the template file -** and writing the results to the generated parser */ -/* The first function transfers data from "in" to "out" until -** a line is seen which begins with "%%". The line number is -** tracked. -** -** if name!=0, then any word that begin with "Parse" is changed to -** begin with *name instead. -*/ -PRIVATE void tplt_xfer(char *name, FILE *in, FILE *out, int *lineno) -{ - int i, iStart; - char line[LINESIZE]; - while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){ - (*lineno)++; - iStart = 0; - if( name ){ - for(i=0; line[i]; i++){ - if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0 - && (i==0 || !isalpha(line[i-1])) - ){ - if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]); - fprintf(out,"%s",name); - i += 4; - iStart = i+1; - } - } - } - fprintf(out,"%s",&line[iStart]); - } -} - -/* The next function finds the template file and opens it, returning -** a pointer to the opened file. */ -PRIVATE FILE *tplt_open(struct lemon *lemp) -{ - static char templatename[] = "lempar.c"; - char buf[1000]; - FILE *in; - char *tpltname; - char *cp; - - /* first, see if user specified a template filename on the command line. */ - if (user_templatename != 0) { - if( access(user_templatename,004)==-1 ){ - fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", - user_templatename); - lemp->errorcnt++; - return 0; - } - in = fopen(user_templatename,"rb"); - if( in==0 ){ - fprintf(stderr,"Can't open the template file \"%s\".\n",user_templatename); - lemp->errorcnt++; - return 0; - } - return in; - } - - cp = strrchr(lemp->filename,'.'); - if( cp ){ - lemon_sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename); - }else{ - lemon_sprintf(buf,"%s.lt",lemp->filename); - } - if( access(buf,004)==0 ){ - tpltname = buf; - }else if( access(templatename,004)==0 ){ - tpltname = templatename; - }else{ - tpltname = pathsearch(lemp->argv0,templatename,0); - } - if( tpltname==0 ){ - fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", - templatename); - lemp->errorcnt++; - return 0; - } - in = fopen(tpltname,"rb"); - if( in==0 ){ - fprintf(stderr,"Can't open the template file \"%s\".\n",templatename); - lemp->errorcnt++; - return 0; - } - return in; -} - -/* Print a #line directive line to the output file. */ -PRIVATE void tplt_linedir(FILE *out, int lineno, char *filename) -{ - fprintf(out,"#line %d \"",lineno); - while( *filename ){ - if( *filename == '\\' ) putc('\\',out); - putc(*filename,out); - filename++; - } - fprintf(out,"\"\n"); -} - -/* Print a string to the file and keep the linenumber up to date */ -PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno) -{ - if( str==0 ) return; - while( *str ){ - putc(*str,out); - if( *str=='\n' ) (*lineno)++; - str++; - } - if( str[-1]!='\n' ){ - putc('\n',out); - (*lineno)++; - } - if (!lemp->nolinenosflag) { - (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); - } - return; -} - -/* -** The following routine emits code for the destructor for the -** symbol sp -*/ -void emit_destructor_code( - FILE *out, - struct symbol *sp, - struct lemon *lemp, - int *lineno -){ - char *cp = 0; - - if( sp->type==TERMINAL ){ - cp = lemp->tokendest; - if( cp==0 ) return; - fprintf(out,"{\n"); (*lineno)++; - }else if( sp->destructor ){ - cp = sp->destructor; - fprintf(out,"{\n"); (*lineno)++; - if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,sp->destLineno,lemp->filename); } - }else if( lemp->vardest ){ - cp = lemp->vardest; - if( cp==0 ) return; - fprintf(out,"{\n"); (*lineno)++; - }else{ - assert( 0 ); /* Cannot happen */ - } - for(; *cp; cp++){ - if( *cp=='$' && cp[1]=='$' ){ - fprintf(out,"(yypminor->yy%d)",sp->dtnum); - cp++; - continue; - } - if( *cp=='\n' ) (*lineno)++; - fputc(*cp,out); - } - fprintf(out,"\n"); (*lineno)++; - if (!lemp->nolinenosflag) { - (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); - } - fprintf(out,"}\n"); (*lineno)++; - return; -} - -/* -** Return TRUE (non-zero) if the given symbol has a destructor. -*/ -int has_destructor(struct symbol *sp, struct lemon *lemp) -{ - int ret; - if( sp->type==TERMINAL ){ - ret = lemp->tokendest!=0; - }else{ - ret = lemp->vardest!=0 || sp->destructor!=0; - } - return ret; -} - -/* -** Append text to a dynamically allocated string. If zText is 0 then -** reset the string to be empty again. Always return the complete text -** of the string (which is overwritten with each call). -** -** n bytes of zText are stored. If n==0 then all of zText up to the first -** \000 terminator is stored. zText can contain up to two instances of -** %d. The values of p1 and p2 are written into the first and second -** %d. -** -** If n==-1, then the previous character is overwritten. -*/ -PRIVATE char *append_str(const char *zText, int n, int p1, int p2){ - static char empty[1] = { 0 }; - static char *z = 0; - static int alloced = 0; - static int used = 0; - int c; - char zInt[40]; - if( zText==0 ){ - used = 0; - return z; - } - if( n<=0 ){ - if( n<0 ){ - used += n; - assert( used>=0 ); - } - n = lemonStrlen(zText); - } - if( (int) (n+sizeof(zInt)*2+used) >= alloced ){ - alloced = n + sizeof(zInt)*2 + used + 200; - z = (char *) realloc(z, alloced); - } - if( z==0 ) return empty; - while( n-- > 0 ){ - c = *(zText++); - if( c=='%' && n>0 && zText[0]=='d' ){ - lemon_sprintf(zInt, "%d", p1); - p1 = p2; - lemon_strcpy(&z[used], zInt); - used += lemonStrlen(&z[used]); - zText++; - n--; - }else{ - z[used++] = c; - } - } - z[used] = 0; - return z; -} - -/* -** zCode is a string that is the action associated with a rule. Expand -** the symbols in this string so that the refer to elements of the parser -** stack. -*/ -PRIVATE void translate_code(struct lemon *lemp, struct rule *rp){ - char *cp, *xp; - int i; - char lhsused = 0; /* True if the LHS element has been used */ - char used[MAXRHS]; /* True for each RHS element which is used */ - - for(i=0; inrhs; i++) used[i] = 0; - lhsused = 0; - - if( rp->code==0 ){ - static char newlinestr[2] = { '\n', '\0' }; - rp->code = newlinestr; - rp->line = rp->ruleline; - } - - append_str(0,0,0,0); - - /* This const cast is wrong but harmless, if we're careful. */ - for(cp=(char *)rp->code; *cp; cp++){ - if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){ - char saved; - for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++); - saved = *xp; - *xp = 0; - if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){ - append_str("yygotominor.yy%d",0,rp->lhs->dtnum,0); - cp = xp; - lhsused = 1; - }else{ - for(i=0; inrhs; i++){ - if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){ - if( cp!=rp->code && cp[-1]=='@' ){ - /* If the argument is of the form @X then substituted - ** the token number of X, not the value of X */ - append_str("yymsp[%d].major",-1,i-rp->nrhs+1,0); - }else{ - struct symbol *sp = rp->rhs[i]; - int dtnum; - if( sp->type==MULTITERMINAL ){ - dtnum = sp->subsym[0]->dtnum; - }else{ - dtnum = sp->dtnum; - } - append_str("yymsp[%d].minor.yy%d",0,i-rp->nrhs+1, dtnum); - } - cp = xp; - used[i] = 1; - break; - } - } - } - *xp = saved; - } - append_str(cp, 1, 0, 0); - } /* End loop */ - - /* Check to make sure the LHS has been used */ - if( rp->lhsalias && !lhsused ){ - ErrorMsg(lemp->filename,rp->ruleline, - "Label \"%s\" for \"%s(%s)\" is never used.", - rp->lhsalias,rp->lhs->name,rp->lhsalias); - lemp->errorcnt++; - } - - /* Generate destructor code for RHS symbols which are not used in the - ** reduce code */ - for(i=0; inrhs; i++){ - if( rp->rhsalias[i] && !used[i] ){ - ErrorMsg(lemp->filename,rp->ruleline, - "Label %s for \"%s(%s)\" is never used.", - rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]); - lemp->errorcnt++; - }else if( rp->rhsalias[i]==0 ){ - if( has_destructor(rp->rhs[i],lemp) ){ - append_str(" yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0, - rp->rhs[i]->index,i-rp->nrhs+1); - }else{ - /* No destructor defined for this term */ - } - } - } - if( rp->code ){ - cp = append_str(0,0,0,0); - rp->code = Strsafe(cp?cp:""); - } -} - -/* -** Generate code which executes when the rule "rp" is reduced. Write -** the code to "out". Make sure lineno stays up-to-date. -*/ -PRIVATE void emit_code( - FILE *out, - struct rule *rp, - struct lemon *lemp, - int *lineno -){ - const char *cp; - - /* Generate code to do the reduce action */ - if( rp->code ){ - if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,rp->line,lemp->filename); } - fprintf(out,"{%s",rp->code); - for(cp=rp->code; *cp; cp++){ - if( *cp=='\n' ) (*lineno)++; - } /* End loop */ - fprintf(out,"}\n"); (*lineno)++; - if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); } - } /* End if( rp->code ) */ - - return; -} - -/* -** Print the definition of the union used for the parser's data stack. -** This union contains fields for every possible data type for tokens -** and nonterminals. In the process of computing and printing this -** union, also set the ".dtnum" field of every terminal and nonterminal -** symbol. -*/ -void print_stack_union( - FILE *out, /* The output stream */ - struct lemon *lemp, /* The main info structure for this parser */ - int *plineno, /* Pointer to the line number */ - int mhflag /* True if generating makeheaders output */ -){ - int lineno = *plineno; /* The line number of the output */ - char **types; /* A hash table of datatypes */ - int arraysize; /* Size of the "types" array */ - int maxdtlength; /* Maximum length of any ".datatype" field. */ - char *stddt; /* Standardized name for a datatype */ - int i,j; /* Loop counters */ - unsigned hash; /* For hashing the name of a type */ - const char *name; /* Name of the parser */ - - /* Allocate and initialize types[] and allocate stddt[] */ - arraysize = lemp->nsymbol * 2; - types = (char**)calloc( arraysize, sizeof(char*) ); - if( types==0 ){ - fprintf(stderr,"Out of memory.\n"); - exit(1); - } - for(i=0; ivartype ){ - maxdtlength = lemonStrlen(lemp->vartype); - } - for(i=0; insymbol; i++){ - int len; - struct symbol *sp = lemp->symbols[i]; - if( sp->datatype==0 ) continue; - len = lemonStrlen(sp->datatype); - if( len>maxdtlength ) maxdtlength = len; - } - stddt = (char*)malloc( maxdtlength*2 + 1 ); - if( stddt==0 ){ - fprintf(stderr,"Out of memory.\n"); - exit(1); - } - - /* Build a hash table of datatypes. The ".dtnum" field of each symbol - ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is - ** used for terminal symbols. If there is no %default_type defined then - ** 0 is also used as the .dtnum value for nonterminals which do not specify - ** a datatype using the %type directive. - */ - for(i=0; insymbol; i++){ - struct symbol *sp = lemp->symbols[i]; - char *cp; - if( sp==lemp->errsym ){ - sp->dtnum = arraysize+1; - continue; - } - if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){ - sp->dtnum = 0; - continue; - } - cp = sp->datatype; - if( cp==0 ) cp = lemp->vartype; - j = 0; - while( isspace(*cp) ) cp++; - while( *cp ) stddt[j++] = *cp++; - while( j>0 && isspace(stddt[j-1]) ) j--; - stddt[j] = 0; - if( lemp->tokentype && strcmp(stddt, lemp->tokentype)==0 ){ - sp->dtnum = 0; - continue; - } - hash = 0; - for(j=0; stddt[j]; j++){ - hash = hash*53 + stddt[j]; - } - hash = (hash & 0x7fffffff)%arraysize; - while( types[hash] ){ - if( strcmp(types[hash],stddt)==0 ){ - sp->dtnum = hash + 1; - break; - } - hash++; - if( hash>=(unsigned)arraysize ) hash = 0; - } - if( types[hash]==0 ){ - sp->dtnum = hash + 1; - types[hash] = (char*)malloc( lemonStrlen(stddt)+1 ); - if( types[hash]==0 ){ - fprintf(stderr,"Out of memory.\n"); - exit(1); - } - lemon_strcpy(types[hash],stddt); - } - } - - /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */ - name = lemp->name ? lemp->name : "Parse"; - lineno = *plineno; - if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; } - fprintf(out,"#define %sTOKENTYPE %s\n",name, - lemp->tokentype?lemp->tokentype:"void*"); lineno++; - if( mhflag ){ fprintf(out,"#endif\n"); lineno++; } - fprintf(out,"typedef union {\n"); lineno++; - fprintf(out," int yyinit;\n"); lineno++; - fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++; - for(i=0; ierrsym->useCnt ){ - fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++; - } - free(stddt); - free(types); - fprintf(out,"} YYMINORTYPE;\n"); lineno++; - *plineno = lineno; -} - -/* -** Return the name of a C datatype able to represent values between -** lwr and upr, inclusive. -*/ -static const char *minimum_size_type(int lwr, int upr){ - if( lwr>=0 ){ - if( upr<=255 ){ - return "unsigned char"; - }else if( upr<65535 ){ - return "unsigned short int"; - }else{ - return "unsigned int"; - } - }else if( lwr>=-127 && upr<=127 ){ - return "signed char"; - }else if( lwr>=-32767 && upr<32767 ){ - return "short"; - }else{ - return "int"; - } -} - -/* -** Each state contains a set of token transaction and a set of -** nonterminal transactions. Each of these sets makes an instance -** of the following structure. An array of these structures is used -** to order the creation of entries in the yy_action[] table. -*/ -struct axset { - struct state *stp; /* A pointer to a state */ - int isTkn; /* True to use tokens. False for non-terminals */ - int nAction; /* Number of actions */ - int iOrder; /* Original order of action sets */ -}; - -/* -** Compare to axset structures for sorting purposes -*/ -static int axset_compare(const void *a, const void *b){ - struct axset *p1 = (struct axset*)a; - struct axset *p2 = (struct axset*)b; - int c; - c = p2->nAction - p1->nAction; - if( c==0 ){ - c = p2->iOrder - p1->iOrder; - } - assert( c!=0 || p1==p2 ); - return c; -} - -/* -** Write text on "out" that describes the rule "rp". -*/ -static void writeRuleText(FILE *out, struct rule *rp){ - int j; - fprintf(out,"%s ::=", rp->lhs->name); - for(j=0; jnrhs; j++){ - struct symbol *sp = rp->rhs[j]; - if( sp->type!=MULTITERMINAL ){ - fprintf(out," %s", sp->name); - }else{ - int k; - fprintf(out," %s", sp->subsym[0]->name); - for(k=1; knsubsym; k++){ - fprintf(out,"|%s",sp->subsym[k]->name); - } - } - } -} - - -/* Generate C source code for the parser */ -void ReportTable( - struct lemon *lemp, - int mhflag /* Output in makeheaders format if true */ -){ - FILE *out, *in; - char line[LINESIZE]; - int lineno; - struct state *stp; - struct action *ap; - struct rule *rp; - struct acttab *pActtab; - int i, j, n; - const char *name; - int mnTknOfst, mxTknOfst; - int mnNtOfst, mxNtOfst; - struct axset *ax; - - in = tplt_open(lemp); - if( in==0 ) return; - out = file_open(lemp,".c","wb"); - if( out==0 ){ - fclose(in); - return; - } - lineno = 1; - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate the include code, if any */ - tplt_print(out,lemp,lemp->include,&lineno); - if( mhflag ){ - char *name = file_makename(lemp, ".h"); - fprintf(out,"#include \"%s\"\n", name); lineno++; - free(name); - } - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate #defines for all tokens */ - if( mhflag ){ - const char *prefix; - fprintf(out,"#if INTERFACE\n"); lineno++; - if( lemp->tokenprefix ) prefix = lemp->tokenprefix; - else prefix = ""; - for(i=1; interminal; i++){ - fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); - lineno++; - } - fprintf(out,"#endif\n"); lineno++; - } - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate the defines */ - fprintf(out,"#define YYCODETYPE %s\n", - minimum_size_type(0, lemp->nsymbol+1)); lineno++; - fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++; - fprintf(out,"#define YYACTIONTYPE %s\n", - minimum_size_type(0, lemp->nstate+lemp->nrule+5)); lineno++; - if( lemp->wildcard ){ - fprintf(out,"#define YYWILDCARD %d\n", - lemp->wildcard->index); lineno++; - } - print_stack_union(out,lemp,&lineno,mhflag); - fprintf(out, "#ifndef YYSTACKDEPTH\n"); lineno++; - if( lemp->stacksize ){ - fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++; - }else{ - fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++; - } - fprintf(out, "#endif\n"); lineno++; - if( mhflag ){ - fprintf(out,"#if INTERFACE\n"); lineno++; - } - name = lemp->name ? lemp->name : "Parse"; - if( lemp->arg && lemp->arg[0] ){ - int i; - i = lemonStrlen(lemp->arg); - while( i>=1 && isspace(lemp->arg[i-1]) ) i--; - while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--; - fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++; - fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++; - fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n", - name,lemp->arg,&lemp->arg[i]); lineno++; - fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n", - name,&lemp->arg[i],&lemp->arg[i]); lineno++; - }else{ - fprintf(out,"#define %sARG_SDECL\n",name); lineno++; - fprintf(out,"#define %sARG_PDECL\n",name); lineno++; - fprintf(out,"#define %sARG_FETCH\n",name); lineno++; - fprintf(out,"#define %sARG_STORE\n",name); lineno++; - } - if( mhflag ){ - fprintf(out,"#endif\n"); lineno++; - } - fprintf(out,"#define YYNSTATE %d\n",lemp->nstate); lineno++; - fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++; - if( lemp->errsym->useCnt ){ - fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; - fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; - } - if( lemp->has_fallback ){ - fprintf(out,"#define YYFALLBACK 1\n"); lineno++; - } - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate the action table and its associates: - ** - ** yy_action[] A single table containing all actions. - ** yy_lookahead[] A table containing the lookahead for each entry in - ** yy_action. Used to detect hash collisions. - ** yy_shift_ofst[] For each state, the offset into yy_action for - ** shifting terminals. - ** yy_reduce_ofst[] For each state, the offset into yy_action for - ** shifting non-terminals after a reduce. - ** yy_default[] Default action for each state. - */ - - /* Compute the actions on all states and count them up */ - ax = (struct axset *) calloc(lemp->nstate*2, sizeof(ax[0])); - if( ax==0 ){ - fprintf(stderr,"malloc failed\n"); - exit(1); - } - for(i=0; instate; i++){ - stp = lemp->sorted[i]; - ax[i*2].stp = stp; - ax[i*2].isTkn = 1; - ax[i*2].nAction = stp->nTknAct; - ax[i*2+1].stp = stp; - ax[i*2+1].isTkn = 0; - ax[i*2+1].nAction = stp->nNtAct; - } - mxTknOfst = mnTknOfst = 0; - mxNtOfst = mnNtOfst = 0; - - /* Compute the action table. In order to try to keep the size of the - ** action table to a minimum, the heuristic of placing the largest action - ** sets first is used. - */ - for(i=0; instate*2; i++) ax[i].iOrder = i; - qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare); - pActtab = acttab_alloc(); - for(i=0; instate*2 && ax[i].nAction>0; i++){ - stp = ax[i].stp; - if( ax[i].isTkn ){ - for(ap=stp->ap; ap; ap=ap->next){ - int action; - if( ap->sp->index>=lemp->nterminal ) continue; - action = compute_action(lemp, ap); - if( action<0 ) continue; - acttab_action(pActtab, ap->sp->index, action); - } - stp->iTknOfst = acttab_insert(pActtab); - if( stp->iTknOfstiTknOfst; - if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst; - }else{ - for(ap=stp->ap; ap; ap=ap->next){ - int action; - if( ap->sp->indexnterminal ) continue; - if( ap->sp->index==lemp->nsymbol ) continue; - action = compute_action(lemp, ap); - if( action<0 ) continue; - acttab_action(pActtab, ap->sp->index, action); - } - stp->iNtOfst = acttab_insert(pActtab); - if( stp->iNtOfstiNtOfst; - if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst; - } - } - free(ax); - - /* Output the yy_action table */ - n = acttab_size(pActtab); - fprintf(out,"#define YY_ACTTAB_COUNT (%d)\n", n); lineno++; - fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++; - for(i=j=0; instate + lemp->nrule + 2; - if( j==0 ) fprintf(out," /* %5d */ ", i); - fprintf(out, " %4d,", action); - if( j==9 || i==n-1 ){ - fprintf(out, "\n"); lineno++; - j = 0; - }else{ - j++; - } - } - fprintf(out, "};\n"); lineno++; - - /* Output the yy_lookahead table */ - fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++; - for(i=j=0; insymbol; - if( j==0 ) fprintf(out," /* %5d */ ", i); - fprintf(out, " %4d,", la); - if( j==9 || i==n-1 ){ - fprintf(out, "\n"); lineno++; - j = 0; - }else{ - j++; - } - } - fprintf(out, "};\n"); lineno++; - - /* Output the yy_shift_ofst[] table */ - fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++; - n = lemp->nstate; - while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--; - fprintf(out, "#define YY_SHIFT_COUNT (%d)\n", n-1); lineno++; - fprintf(out, "#define YY_SHIFT_MIN (%d)\n", mnTknOfst); lineno++; - fprintf(out, "#define YY_SHIFT_MAX (%d)\n", mxTknOfst); lineno++; - fprintf(out, "static const %s yy_shift_ofst[] = {\n", - minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++; - for(i=j=0; isorted[i]; - ofst = stp->iTknOfst; - if( ofst==NO_OFFSET ) ofst = mnTknOfst - 1; - if( j==0 ) fprintf(out," /* %5d */ ", i); - fprintf(out, " %4d,", ofst); - if( j==9 || i==n-1 ){ - fprintf(out, "\n"); lineno++; - j = 0; - }else{ - j++; - } - } - fprintf(out, "};\n"); lineno++; - - /* Output the yy_reduce_ofst[] table */ - fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++; - n = lemp->nstate; - while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--; - fprintf(out, "#define YY_REDUCE_COUNT (%d)\n", n-1); lineno++; - fprintf(out, "#define YY_REDUCE_MIN (%d)\n", mnNtOfst); lineno++; - fprintf(out, "#define YY_REDUCE_MAX (%d)\n", mxNtOfst); lineno++; - fprintf(out, "static const %s yy_reduce_ofst[] = {\n", - minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++; - for(i=j=0; isorted[i]; - ofst = stp->iNtOfst; - if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1; - if( j==0 ) fprintf(out," /* %5d */ ", i); - fprintf(out, " %4d,", ofst); - if( j==9 || i==n-1 ){ - fprintf(out, "\n"); lineno++; - j = 0; - }else{ - j++; - } - } - fprintf(out, "};\n"); lineno++; - - /* Output the default action table */ - fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++; - n = lemp->nstate; - for(i=j=0; isorted[i]; - if( j==0 ) fprintf(out," /* %5d */ ", i); - fprintf(out, " %4d,", stp->iDflt); - if( j==9 || i==n-1 ){ - fprintf(out, "\n"); lineno++; - j = 0; - }else{ - j++; - } - } - fprintf(out, "};\n"); lineno++; - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate the table of fallback tokens. - */ - if( lemp->has_fallback ){ - int mx = lemp->nterminal - 1; - while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; } - for(i=0; i<=mx; i++){ - struct symbol *p = lemp->symbols[i]; - if( p->fallback==0 ){ - fprintf(out, " 0, /* %10s => nothing */\n", p->name); - }else{ - fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index, - p->name, p->fallback->name); - } - lineno++; - } - } - tplt_xfer(lemp->name, in, out, &lineno); - - /* Generate a table containing the symbolic name of every symbol - */ - for(i=0; insymbol; i++){ - lemon_sprintf(line,"\"%s\",",lemp->symbols[i]->name); - fprintf(out," %-15s",line); - if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; } - } - if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; } - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate a table containing a text string that describes every - ** rule in the rule set of the grammar. This information is used - ** when tracing REDUCE actions. - */ - for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){ - assert( rp->index==i ); - fprintf(out," /* %3d */ \"", i); - writeRuleText(out, rp); - fprintf(out,"\",\n"); lineno++; - } - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate code which executes every time a symbol is popped from - ** the stack while processing errors or while destroying the parser. - ** (In other words, generate the %destructor actions) - */ - if( lemp->tokendest ){ - int once = 1; - for(i=0; insymbol; i++){ - struct symbol *sp = lemp->symbols[i]; - if( sp==0 || sp->type!=TERMINAL ) continue; - if( once ){ - fprintf(out, " /* TERMINAL Destructor */\n"); lineno++; - once = 0; - } - fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; - } - for(i=0; insymbol && lemp->symbols[i]->type!=TERMINAL; i++); - if( insymbol ){ - emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); - fprintf(out," break;\n"); lineno++; - } - } - if( lemp->vardest ){ - struct symbol *dflt_sp = 0; - int once = 1; - for(i=0; insymbol; i++){ - struct symbol *sp = lemp->symbols[i]; - if( sp==0 || sp->type==TERMINAL || - sp->index<=0 || sp->destructor!=0 ) continue; - if( once ){ - fprintf(out, " /* Default NON-TERMINAL Destructor */\n"); lineno++; - once = 0; - } - fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; - dflt_sp = sp; - } - if( dflt_sp!=0 ){ - emit_destructor_code(out,dflt_sp,lemp,&lineno); - } - fprintf(out," break;\n"); lineno++; - } - for(i=0; insymbol; i++){ - struct symbol *sp = lemp->symbols[i]; - if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue; - fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; - - /* Combine duplicate destructors into a single case */ - for(j=i+1; jnsymbol; j++){ - struct symbol *sp2 = lemp->symbols[j]; - if( sp2 && sp2->type!=TERMINAL && sp2->destructor - && sp2->dtnum==sp->dtnum - && strcmp(sp->destructor,sp2->destructor)==0 ){ - fprintf(out," case %d: /* %s */\n", - sp2->index, sp2->name); lineno++; - sp2->destructor = 0; - } - } - - emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); - fprintf(out," break;\n"); lineno++; - } - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate code which executes whenever the parser stack overflows */ - tplt_print(out,lemp,lemp->overflow,&lineno); - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate the table of rule information - ** - ** Note: This code depends on the fact that rules are number - ** sequentually beginning with 0. - */ - for(rp=lemp->rule; rp; rp=rp->next){ - fprintf(out," { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++; - } - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate code which execution during each REDUCE action */ - for(rp=lemp->rule; rp; rp=rp->next){ - translate_code(lemp, rp); - } - /* First output rules other than the default: rule */ - for(rp=lemp->rule; rp; rp=rp->next){ - struct rule *rp2; /* Other rules with the same action */ - if( rp->code==0 ) continue; - if( rp->code[0]=='\n' && rp->code[1]==0 ) continue; /* Will be default: */ - fprintf(out," case %d: /* ", rp->index); - writeRuleText(out, rp); - fprintf(out, " */\n"); lineno++; - for(rp2=rp->next; rp2; rp2=rp2->next){ - if( rp2->code==rp->code ){ - fprintf(out," case %d: /* ", rp2->index); - writeRuleText(out, rp2); - fprintf(out," */ yytestcase(yyruleno==%d);\n", rp2->index); lineno++; - rp2->code = 0; - } - } - emit_code(out,rp,lemp,&lineno); - fprintf(out," break;\n"); lineno++; - rp->code = 0; - } - /* Finally, output the default: rule. We choose as the default: all - ** empty actions. */ - fprintf(out," default:\n"); lineno++; - for(rp=lemp->rule; rp; rp=rp->next){ - if( rp->code==0 ) continue; - assert( rp->code[0]=='\n' && rp->code[1]==0 ); - fprintf(out," /* (%d) ", rp->index); - writeRuleText(out, rp); - fprintf(out, " */ yytestcase(yyruleno==%d);\n", rp->index); lineno++; - } - fprintf(out," break;\n"); lineno++; - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate code which executes if a parse fails */ - tplt_print(out,lemp,lemp->failure,&lineno); - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate code which executes when a syntax error occurs */ - tplt_print(out,lemp,lemp->error,&lineno); - tplt_xfer(lemp->name,in,out,&lineno); - - /* Generate code which executes when the parser accepts its input */ - tplt_print(out,lemp,lemp->accept,&lineno); - tplt_xfer(lemp->name,in,out,&lineno); - - /* Append any addition code the user desires */ - tplt_print(out,lemp,lemp->extracode,&lineno); - - fclose(in); - fclose(out); - return; -} - -/* Generate a header file for the parser */ -void ReportHeader(struct lemon *lemp) -{ - FILE *out, *in; - const char *prefix; - char line[LINESIZE]; - char pattern[LINESIZE]; - int i; - - if( lemp->tokenprefix ) prefix = lemp->tokenprefix; - else prefix = ""; - in = file_open(lemp,".h","rb"); - if( in ){ - int nextChar; - for(i=1; interminal && fgets(line,LINESIZE,in); i++){ - lemon_sprintf(pattern,"#define %s%-30s %3d\n", - prefix,lemp->symbols[i]->name,i); - if( strcmp(line,pattern) ) break; - } - nextChar = fgetc(in); - fclose(in); - if( i==lemp->nterminal && nextChar==EOF ){ - /* No change in the file. Don't rewrite it. */ - return; - } - } - out = file_open(lemp,".h","wb"); - if( out ){ - for(i=1; interminal; i++){ - fprintf(out,"#define %s%-30s %3d\n",prefix,lemp->symbols[i]->name,i); - } - fclose(out); - } - return; -} - -/* Reduce the size of the action tables, if possible, by making use -** of defaults. -** -** In this version, we take the most frequent REDUCE action and make -** it the default. Except, there is no default if the wildcard token -** is a possible look-ahead. -*/ -void CompressTables(struct lemon *lemp) -{ - struct state *stp; - struct action *ap, *ap2; - struct rule *rp, *rp2, *rbest; - int nbest, n; - int i; - int usesWildcard; - - for(i=0; instate; i++){ - stp = lemp->sorted[i]; - nbest = 0; - rbest = 0; - usesWildcard = 0; - - for(ap=stp->ap; ap; ap=ap->next){ - if( ap->type==SHIFT && ap->sp==lemp->wildcard ){ - usesWildcard = 1; - } - if( ap->type!=REDUCE ) continue; - rp = ap->x.rp; - if( rp->lhsStart ) continue; - if( rp==rbest ) continue; - n = 1; - for(ap2=ap->next; ap2; ap2=ap2->next){ - if( ap2->type!=REDUCE ) continue; - rp2 = ap2->x.rp; - if( rp2==rbest ) continue; - if( rp2==rp ) n++; - } - if( n>nbest ){ - nbest = n; - rbest = rp; - } - } - - /* Do not make a default if the number of rules to default - ** is not at least 1 or if the wildcard token is a possible - ** lookahead. - */ - if( nbest<1 || usesWildcard ) continue; - - - /* Combine matching REDUCE actions into a single default */ - for(ap=stp->ap; ap; ap=ap->next){ - if( ap->type==REDUCE && ap->x.rp==rbest ) break; - } - assert( ap ); - ap->sp = Symbol_new("{default}"); - for(ap=ap->next; ap; ap=ap->next){ - if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED; - } - stp->ap = Action_sort(stp->ap); - } -} - - -/* -** Compare two states for sorting purposes. The smaller state is the -** one with the most non-terminal actions. If they have the same number -** of non-terminal actions, then the smaller is the one with the most -** token actions. -*/ -static int stateResortCompare(const void *a, const void *b){ - const struct state *pA = *(const struct state**)a; - const struct state *pB = *(const struct state**)b; - int n; - - n = pB->nNtAct - pA->nNtAct; - if( n==0 ){ - n = pB->nTknAct - pA->nTknAct; - if( n==0 ){ - n = pB->statenum - pA->statenum; - } - } - assert( n!=0 ); - return n; -} - - -/* -** Renumber and resort states so that states with fewer choices -** occur at the end. Except, keep state 0 as the first state. -*/ -void ResortStates(struct lemon *lemp) -{ - int i; - struct state *stp; - struct action *ap; - - for(i=0; instate; i++){ - stp = lemp->sorted[i]; - stp->nTknAct = stp->nNtAct = 0; - stp->iDflt = lemp->nstate + lemp->nrule; - stp->iTknOfst = NO_OFFSET; - stp->iNtOfst = NO_OFFSET; - for(ap=stp->ap; ap; ap=ap->next){ - if( compute_action(lemp,ap)>=0 ){ - if( ap->sp->indexnterminal ){ - stp->nTknAct++; - }else if( ap->sp->indexnsymbol ){ - stp->nNtAct++; - }else{ - stp->iDflt = compute_action(lemp, ap); - } - } - } - } - qsort(&lemp->sorted[1], lemp->nstate-1, sizeof(lemp->sorted[0]), - stateResortCompare); - for(i=0; instate; i++){ - lemp->sorted[i]->statenum = i; - } -} - - -/***************** From the file "set.c" ************************************/ -/* -** Set manipulation routines for the LEMON parser generator. -*/ - -static int size = 0; - -/* Set the set size */ -void SetSize(int n) -{ - size = n+1; -} - -/* Allocate a new set */ -char *SetNew(){ - char *s; - s = (char*)calloc( size, 1); - if( s==0 ){ - extern void memory_error(); - memory_error(); - } - return s; -} - -/* Deallocate a set */ -void SetFree(char *s) -{ - free(s); -} - -/* Add a new element to the set. Return TRUE if the element was added -** and FALSE if it was already there. */ -int SetAdd(char *s, int e) -{ - int rv; - assert( e>=0 && esize = 1024; - x1a->count = 0; - x1a->tbl = (x1node*)calloc(1024, sizeof(x1node) + sizeof(x1node*)); - if( x1a->tbl==0 ){ - free(x1a); - x1a = 0; - }else{ - int i; - x1a->ht = (x1node**)&(x1a->tbl[1024]); - for(i=0; i<1024; i++) x1a->ht[i] = 0; - } - } -} -/* Insert a new record into the array. Return TRUE if successful. -** Prior data with the same key is NOT overwritten */ -int Strsafe_insert(const char *data) -{ - x1node *np; - unsigned h; - unsigned ph; - - if( x1a==0 ) return 0; - ph = strhash(data); - h = ph & (x1a->size-1); - np = x1a->ht[h]; - while( np ){ - if( strcmp(np->data,data)==0 ){ - /* An existing entry with the same key is found. */ - /* Fail because overwrite is not allows. */ - return 0; - } - np = np->next; - } - if( x1a->count>=x1a->size ){ - /* Need to make the hash table bigger */ - int i,size; - struct s_x1 array; - array.size = size = x1a->size*2; - array.count = x1a->count; - array.tbl = (x1node*)calloc(size, sizeof(x1node) + sizeof(x1node*)); - if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ - array.ht = (x1node**)&(array.tbl[size]); - for(i=0; icount; i++){ - x1node *oldnp, *newnp; - oldnp = &(x1a->tbl[i]); - h = strhash(oldnp->data) & (size-1); - newnp = &(array.tbl[i]); - if( array.ht[h] ) array.ht[h]->from = &(newnp->next); - newnp->next = array.ht[h]; - newnp->data = oldnp->data; - newnp->from = &(array.ht[h]); - array.ht[h] = newnp; - } - free(x1a->tbl); - *x1a = array; - } - /* Insert the new data */ - h = ph & (x1a->size-1); - np = &(x1a->tbl[x1a->count++]); - np->data = data; - if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next); - np->next = x1a->ht[h]; - x1a->ht[h] = np; - np->from = &(x1a->ht[h]); - return 1; -} - -/* Return a pointer to data assigned to the given key. Return NULL -** if no such key. */ -const char *Strsafe_find(const char *key) -{ - unsigned h; - x1node *np; - - if( x1a==0 ) return 0; - h = strhash(key) & (x1a->size-1); - np = x1a->ht[h]; - while( np ){ - if( strcmp(np->data,key)==0 ) break; - np = np->next; - } - return np ? np->data : 0; -} - -/* Return a pointer to the (terminal or nonterminal) symbol "x". -** Create a new symbol if this is the first time "x" has been seen. -*/ -struct symbol *Symbol_new(const char *x) -{ - struct symbol *sp; - - sp = Symbol_find(x); - if( sp==0 ){ - sp = (struct symbol *)calloc(1, sizeof(struct symbol) ); - MemoryCheck(sp); - sp->name = Strsafe(x); - sp->type = isupper(*x) ? TERMINAL : NONTERMINAL; - sp->rule = 0; - sp->fallback = 0; - sp->prec = -1; - sp->assoc = UNK; - sp->firstset = 0; - sp->lambda = LEMON_FALSE; - sp->destructor = 0; - sp->destLineno = 0; - sp->datatype = 0; - sp->useCnt = 0; - Symbol_insert(sp,sp->name); - } - sp->useCnt++; - return sp; -} - -/* Compare two symbols for sorting purposes. Return negative, -** zero, or positive if a is less then, equal to, or greater -** than b. -** -** Symbols that begin with upper case letters (terminals or tokens) -** must sort before symbols that begin with lower case letters -** (non-terminals). And MULTITERMINAL symbols (created using the -** %token_class directive) must sort at the very end. Other than -** that, the order does not matter. -** -** We find experimentally that leaving the symbols in their original -** order (the order they appeared in the grammar file) gives the -** smallest parser tables in SQLite. -*/ -int Symbolcmpp(const void *_a, const void *_b) -{ - const struct symbol *a = *(const struct symbol **) _a; - const struct symbol *b = *(const struct symbol **) _b; - int i1 = a->type==MULTITERMINAL ? 3 : a->name[0]>'Z' ? 2 : 1; - int i2 = b->type==MULTITERMINAL ? 3 : b->name[0]>'Z' ? 2 : 1; - return i1==i2 ? a->index - b->index : i1 - i2; -} - -/* There is one instance of the following structure for each -** associative array of type "x2". -*/ -struct s_x2 { - int size; /* The number of available slots. */ - /* Must be a power of 2 greater than or */ - /* equal to 1 */ - int count; /* Number of currently slots filled */ - struct s_x2node *tbl; /* The data stored here */ - struct s_x2node **ht; /* Hash table for lookups */ -}; - -/* There is one instance of this structure for every data element -** in an associative array of type "x2". -*/ -typedef struct s_x2node { - struct symbol *data; /* The data */ - const char *key; /* The key */ - struct s_x2node *next; /* Next entry with the same hash */ - struct s_x2node **from; /* Previous link */ -} x2node; - -/* There is only one instance of the array, which is the following */ -static struct s_x2 *x2a; - -/* Allocate a new associative array */ -void Symbol_init(){ - if( x2a ) return; - x2a = (struct s_x2*)malloc( sizeof(struct s_x2) ); - if( x2a ){ - x2a->size = 128; - x2a->count = 0; - x2a->tbl = (x2node*)calloc(128, sizeof(x2node) + sizeof(x2node*)); - if( x2a->tbl==0 ){ - free(x2a); - x2a = 0; - }else{ - int i; - x2a->ht = (x2node**)&(x2a->tbl[128]); - for(i=0; i<128; i++) x2a->ht[i] = 0; - } - } -} -/* Insert a new record into the array. Return TRUE if successful. -** Prior data with the same key is NOT overwritten */ -int Symbol_insert(struct symbol *data, const char *key) -{ - x2node *np; - unsigned h; - unsigned ph; - - if( x2a==0 ) return 0; - ph = strhash(key); - h = ph & (x2a->size-1); - np = x2a->ht[h]; - while( np ){ - if( strcmp(np->key,key)==0 ){ - /* An existing entry with the same key is found. */ - /* Fail because overwrite is not allows. */ - return 0; - } - np = np->next; - } - if( x2a->count>=x2a->size ){ - /* Need to make the hash table bigger */ - int i,size; - struct s_x2 array; - array.size = size = x2a->size*2; - array.count = x2a->count; - array.tbl = (x2node*)calloc(size, sizeof(x2node) + sizeof(x2node*)); - if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ - array.ht = (x2node**)&(array.tbl[size]); - for(i=0; icount; i++){ - x2node *oldnp, *newnp; - oldnp = &(x2a->tbl[i]); - h = strhash(oldnp->key) & (size-1); - newnp = &(array.tbl[i]); - if( array.ht[h] ) array.ht[h]->from = &(newnp->next); - newnp->next = array.ht[h]; - newnp->key = oldnp->key; - newnp->data = oldnp->data; - newnp->from = &(array.ht[h]); - array.ht[h] = newnp; - } - free(x2a->tbl); - *x2a = array; - } - /* Insert the new data */ - h = ph & (x2a->size-1); - np = &(x2a->tbl[x2a->count++]); - np->key = key; - np->data = data; - if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next); - np->next = x2a->ht[h]; - x2a->ht[h] = np; - np->from = &(x2a->ht[h]); - return 1; -} - -/* Return a pointer to data assigned to the given key. Return NULL -** if no such key. */ -struct symbol *Symbol_find(const char *key) -{ - unsigned h; - x2node *np; - - if( x2a==0 ) return 0; - h = strhash(key) & (x2a->size-1); - np = x2a->ht[h]; - while( np ){ - if( strcmp(np->key,key)==0 ) break; - np = np->next; - } - return np ? np->data : 0; -} - -/* Return the n-th data. Return NULL if n is out of range. */ -struct symbol *Symbol_Nth(int n) -{ - struct symbol *data; - if( x2a && n>0 && n<=x2a->count ){ - data = x2a->tbl[n-1].data; - }else{ - data = 0; - } - return data; -} - -/* Return the size of the array */ -int Symbol_count() -{ - return x2a ? x2a->count : 0; -} - -/* Return an array of pointers to all data in the table. -** The array is obtained from malloc. Return NULL if memory allocation -** problems, or if the array is empty. */ -struct symbol **Symbol_arrayof() -{ - struct symbol **array; - int i,size; - if( x2a==0 ) return 0; - size = x2a->count; - array = (struct symbol **)calloc(size, sizeof(struct symbol *)); - if( array ){ - for(i=0; itbl[i].data; - } - return array; -} - -/* Compare two configurations */ -int Configcmp(const char *_a,const char *_b) -{ - const struct config *a = (struct config *) _a; - const struct config *b = (struct config *) _b; - int x; - x = a->rp->index - b->rp->index; - if( x==0 ) x = a->dot - b->dot; - return x; -} - -/* Compare two states */ -PRIVATE int statecmp(struct config *a, struct config *b) -{ - int rc; - for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){ - rc = a->rp->index - b->rp->index; - if( rc==0 ) rc = a->dot - b->dot; - } - if( rc==0 ){ - if( a ) rc = 1; - if( b ) rc = -1; - } - return rc; -} - -/* Hash a state */ -PRIVATE unsigned statehash(struct config *a) -{ - unsigned h=0; - while( a ){ - h = h*571 + a->rp->index*37 + a->dot; - a = a->bp; - } - return h; -} - -/* Allocate a new state structure */ -struct state *State_new() -{ - struct state *newstate; - newstate = (struct state *)calloc(1, sizeof(struct state) ); - MemoryCheck(newstate); - return newstate; -} - -/* There is one instance of the following structure for each -** associative array of type "x3". -*/ -struct s_x3 { - int size; /* The number of available slots. */ - /* Must be a power of 2 greater than or */ - /* equal to 1 */ - int count; /* Number of currently slots filled */ - struct s_x3node *tbl; /* The data stored here */ - struct s_x3node **ht; /* Hash table for lookups */ -}; - -/* There is one instance of this structure for every data element -** in an associative array of type "x3". -*/ -typedef struct s_x3node { - struct state *data; /* The data */ - struct config *key; /* The key */ - struct s_x3node *next; /* Next entry with the same hash */ - struct s_x3node **from; /* Previous link */ -} x3node; - -/* There is only one instance of the array, which is the following */ -static struct s_x3 *x3a; - -/* Allocate a new associative array */ -void State_init(){ - if( x3a ) return; - x3a = (struct s_x3*)malloc( sizeof(struct s_x3) ); - if( x3a ){ - x3a->size = 128; - x3a->count = 0; - x3a->tbl = (x3node*)calloc(128, sizeof(x3node) + sizeof(x3node*)); - if( x3a->tbl==0 ){ - free(x3a); - x3a = 0; - }else{ - int i; - x3a->ht = (x3node**)&(x3a->tbl[128]); - for(i=0; i<128; i++) x3a->ht[i] = 0; - } - } -} -/* Insert a new record into the array. Return TRUE if successful. -** Prior data with the same key is NOT overwritten */ -int State_insert(struct state *data, struct config *key) -{ - x3node *np; - unsigned h; - unsigned ph; - - if( x3a==0 ) return 0; - ph = statehash(key); - h = ph & (x3a->size-1); - np = x3a->ht[h]; - while( np ){ - if( statecmp(np->key,key)==0 ){ - /* An existing entry with the same key is found. */ - /* Fail because overwrite is not allows. */ - return 0; - } - np = np->next; - } - if( x3a->count>=x3a->size ){ - /* Need to make the hash table bigger */ - int i,size; - struct s_x3 array; - array.size = size = x3a->size*2; - array.count = x3a->count; - array.tbl = (x3node*)calloc(size, sizeof(x3node) + sizeof(x3node*)); - if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ - array.ht = (x3node**)&(array.tbl[size]); - for(i=0; icount; i++){ - x3node *oldnp, *newnp; - oldnp = &(x3a->tbl[i]); - h = statehash(oldnp->key) & (size-1); - newnp = &(array.tbl[i]); - if( array.ht[h] ) array.ht[h]->from = &(newnp->next); - newnp->next = array.ht[h]; - newnp->key = oldnp->key; - newnp->data = oldnp->data; - newnp->from = &(array.ht[h]); - array.ht[h] = newnp; - } - free(x3a->tbl); - *x3a = array; - } - /* Insert the new data */ - h = ph & (x3a->size-1); - np = &(x3a->tbl[x3a->count++]); - np->key = key; - np->data = data; - if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next); - np->next = x3a->ht[h]; - x3a->ht[h] = np; - np->from = &(x3a->ht[h]); - return 1; -} - -/* Return a pointer to data assigned to the given key. Return NULL -** if no such key. */ -struct state *State_find(struct config *key) -{ - unsigned h; - x3node *np; - - if( x3a==0 ) return 0; - h = statehash(key) & (x3a->size-1); - np = x3a->ht[h]; - while( np ){ - if( statecmp(np->key,key)==0 ) break; - np = np->next; - } - return np ? np->data : 0; -} - -/* Return an array of pointers to all data in the table. -** The array is obtained from malloc. Return NULL if memory allocation -** problems, or if the array is empty. */ -struct state **State_arrayof() -{ - struct state **array; - int i,size; - if( x3a==0 ) return 0; - size = x3a->count; - array = (struct state **)calloc(size, sizeof(struct state *)); - if( array ){ - for(i=0; itbl[i].data; - } - return array; -} - -/* Hash a configuration */ -PRIVATE unsigned confighash(struct config *a) -{ - unsigned h=0; - h = h*571 + a->rp->index*37 + a->dot; - return h; -} - -/* There is one instance of the following structure for each -** associative array of type "x4". -*/ -struct s_x4 { - int size; /* The number of available slots. */ - /* Must be a power of 2 greater than or */ - /* equal to 1 */ - int count; /* Number of currently slots filled */ - struct s_x4node *tbl; /* The data stored here */ - struct s_x4node **ht; /* Hash table for lookups */ -}; - -/* There is one instance of this structure for every data element -** in an associative array of type "x4". -*/ -typedef struct s_x4node { - struct config *data; /* The data */ - struct s_x4node *next; /* Next entry with the same hash */ - struct s_x4node **from; /* Previous link */ -} x4node; - -/* There is only one instance of the array, which is the following */ -static struct s_x4 *x4a; - -/* Allocate a new associative array */ -void Configtable_init(){ - if( x4a ) return; - x4a = (struct s_x4*)malloc( sizeof(struct s_x4) ); - if( x4a ){ - x4a->size = 64; - x4a->count = 0; - x4a->tbl = (x4node*)calloc(64, sizeof(x4node) + sizeof(x4node*)); - if( x4a->tbl==0 ){ - free(x4a); - x4a = 0; - }else{ - int i; - x4a->ht = (x4node**)&(x4a->tbl[64]); - for(i=0; i<64; i++) x4a->ht[i] = 0; - } - } -} -/* Insert a new record into the array. Return TRUE if successful. -** Prior data with the same key is NOT overwritten */ -int Configtable_insert(struct config *data) -{ - x4node *np; - unsigned h; - unsigned ph; - - if( x4a==0 ) return 0; - ph = confighash(data); - h = ph & (x4a->size-1); - np = x4a->ht[h]; - while( np ){ - if( Configcmp((const char *) np->data,(const char *) data)==0 ){ - /* An existing entry with the same key is found. */ - /* Fail because overwrite is not allows. */ - return 0; - } - np = np->next; - } - if( x4a->count>=x4a->size ){ - /* Need to make the hash table bigger */ - int i,size; - struct s_x4 array; - array.size = size = x4a->size*2; - array.count = x4a->count; - array.tbl = (x4node*)calloc(size, sizeof(x4node) + sizeof(x4node*)); - if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ - array.ht = (x4node**)&(array.tbl[size]); - for(i=0; icount; i++){ - x4node *oldnp, *newnp; - oldnp = &(x4a->tbl[i]); - h = confighash(oldnp->data) & (size-1); - newnp = &(array.tbl[i]); - if( array.ht[h] ) array.ht[h]->from = &(newnp->next); - newnp->next = array.ht[h]; - newnp->data = oldnp->data; - newnp->from = &(array.ht[h]); - array.ht[h] = newnp; - } - free(x4a->tbl); - *x4a = array; - } - /* Insert the new data */ - h = ph & (x4a->size-1); - np = &(x4a->tbl[x4a->count++]); - np->data = data; - if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next); - np->next = x4a->ht[h]; - x4a->ht[h] = np; - np->from = &(x4a->ht[h]); - return 1; -} - -/* Return a pointer to data assigned to the given key. Return NULL -** if no such key. */ -struct config *Configtable_find(struct config *key) -{ - int h; - x4node *np; - - if( x4a==0 ) return 0; - h = confighash(key) & (x4a->size-1); - np = x4a->ht[h]; - while( np ){ - if( Configcmp((const char *) np->data,(const char *) key)==0 ) break; - np = np->next; - } - return np ? np->data : 0; -} - -/* Remove all data from the table. Pass each data to the function "f" -** as it is removed. ("f" may be null to avoid this step.) */ -void Configtable_clear(int(*f)(struct config *)) -{ - int i; - if( x4a==0 || x4a->count==0 ) return; - if( f ) for(i=0; icount; i++) (*f)(x4a->tbl[i].data); - for(i=0; isize; i++) x4a->ht[i] = 0; - x4a->count = 0; - return; -} diff --git a/modules/luci-base-ucode/src/contrib/lempar.c b/modules/luci-base-ucode/src/contrib/lempar.c deleted file mode 100644 index a4e3c07ddb..0000000000 --- a/modules/luci-base-ucode/src/contrib/lempar.c +++ /dev/null @@ -1,851 +0,0 @@ -/* Driver template for the LEMON parser generator. -** The author disclaims copyright to this source code. -*/ -/* First off, code is included that follows the "include" declaration -** in the input grammar file. */ -#include -%% -/* Next is all token values, in a form suitable for use by makeheaders. -** This section will be null unless lemon is run with the -m switch. -*/ -/* -** These constants (all generated automatically by the parser generator) -** specify the various kinds of tokens (terminals) that the parser -** understands. -** -** Each symbol here is a terminal symbol in the grammar. -*/ -%% -/* Make sure the INTERFACE macro is defined. -*/ -#ifndef INTERFACE -# define INTERFACE 1 -#endif -/* The next thing included is series of defines which control -** various aspects of the generated parser. -** YYCODETYPE is the data type used for storing terminal -** and nonterminal numbers. "unsigned char" is -** used if there are fewer than 250 terminals -** and nonterminals. "int" is used otherwise. -** YYNOCODE is a number of type YYCODETYPE which corresponds -** to no legal terminal or nonterminal number. This -** number is used to fill in empty slots of the hash -** table. -** YYFALLBACK If defined, this indicates that one or more tokens -** have fall-back values which should be used if the -** original value of the token will not parse. -** YYACTIONTYPE is the data type used for storing terminal -** and nonterminal numbers. "unsigned char" is -** used if there are fewer than 250 rules and -** states combined. "int" is used otherwise. -** ParseTOKENTYPE is the data type used for minor tokens given -** directly to the parser from the tokenizer. -** YYMINORTYPE is the data type used for all minor tokens. -** This is typically a union of many types, one of -** which is ParseTOKENTYPE. The entry in the union -** for base tokens is called "yy0". -** YYSTACKDEPTH is the maximum depth of the parser's stack. If -** zero the stack is dynamically sized using realloc() -** ParseARG_SDECL A static variable declaration for the %extra_argument -** ParseARG_PDECL A parameter declaration for the %extra_argument -** ParseARG_STORE Code to store %extra_argument into yypParser -** ParseARG_FETCH Code to extract %extra_argument from yypParser -** YYNSTATE the combined number of states. -** YYNRULE the number of rules in the grammar -** YYERRORSYMBOL is the code number of the error symbol. If not -** defined, then do no error processing. -*/ -%% -#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) -#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) -#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) - -/* The yyzerominor constant is used to initialize instances of -** YYMINORTYPE objects to zero. */ -static const YYMINORTYPE yyzerominor = { 0 }; - -/* Define the yytestcase() macro to be a no-op if is not already defined -** otherwise. -** -** Applications can choose to define yytestcase() in the %include section -** to a macro that can assist in verifying code coverage. For production -** code the yytestcase() macro should be turned off. But it is useful -** for testing. -*/ -#ifndef yytestcase -# define yytestcase(X) -#endif - - -/* Next are the tables used to determine what action to take based on the -** current state and lookahead token. These tables are used to implement -** functions that take a state number and lookahead value and return an -** action integer. -** -** Suppose the action integer is N. Then the action is determined as -** follows -** -** 0 <= N < YYNSTATE Shift N. That is, push the lookahead -** token onto the stack and goto state N. -** -** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. -** -** N == YYNSTATE+YYNRULE A syntax error has occurred. -** -** N == YYNSTATE+YYNRULE+1 The parser accepts its input. -** -** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused -** slots in the yy_action[] table. -** -** The action table is constructed as a single large table named yy_action[]. -** Given state S and lookahead X, the action is computed as -** -** yy_action[ yy_shift_ofst[S] + X ] -** -** If the index value yy_shift_ofst[S]+X is out of range or if the value -** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] -** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table -** and that yy_default[S] should be used instead. -** -** The formula above is for computing the action when the lookahead is -** a terminal symbol. If the lookahead is a non-terminal (as occurs after -** a reduce action) then the yy_reduce_ofst[] array is used in place of -** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of -** YY_SHIFT_USE_DFLT. -** -** The following are the tables generated in this section: -** -** yy_action[] A single table containing all actions. -** yy_lookahead[] A table containing the lookahead for each entry in -** yy_action. Used to detect hash collisions. -** yy_shift_ofst[] For each state, the offset into yy_action for -** shifting terminals. -** yy_reduce_ofst[] For each state, the offset into yy_action for -** shifting non-terminals after a reduce. -** yy_default[] Default action for each state. -*/ -%% - -/* The next table maps tokens into fallback tokens. If a construct -** like the following: -** -** %fallback ID X Y Z. -** -** appears in the grammar, then ID becomes a fallback token for X, Y, -** and Z. Whenever one of the tokens X, Y, or Z is input to the parser -** but it does not parse, the type of the token is changed to ID and -** the parse is retried before an error is thrown. -*/ -#ifdef YYFALLBACK -static const YYCODETYPE yyFallback[] = { -%% -}; -#endif /* YYFALLBACK */ - -/* The following structure represents a single element of the -** parser's stack. Information stored includes: -** -** + The state number for the parser at this level of the stack. -** -** + The value of the token stored at this level of the stack. -** (In other words, the "major" token.) -** -** + The semantic value stored at this level of the stack. This is -** the information used by the action routines in the grammar. -** It is sometimes called the "minor" token. -*/ -struct yyStackEntry { - YYACTIONTYPE stateno; /* The state-number */ - YYCODETYPE major; /* The major token value. This is the code - ** number for the token at this stack level */ - YYMINORTYPE minor; /* The user-supplied minor token value. This - ** is the value of the token */ -}; -typedef struct yyStackEntry yyStackEntry; - -/* The state of the parser is completely contained in an instance of -** the following structure */ -struct yyParser { - int yyidx; /* Index of top element in stack */ -#ifdef YYTRACKMAXSTACKDEPTH - int yyidxMax; /* Maximum value of yyidx */ -#endif - int yyerrcnt; /* Shifts left before out of the error */ - ParseARG_SDECL /* A place to hold %extra_argument */ -#if YYSTACKDEPTH<=0 - int yystksz; /* Current side of the stack */ - yyStackEntry *yystack; /* The parser's stack */ -#else - yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ -#endif -}; -typedef struct yyParser yyParser; - -#ifndef NDEBUG -#include -static FILE *yyTraceFILE = 0; -static char *yyTracePrompt = 0; -#endif /* NDEBUG */ - -#ifndef NDEBUG -/* -** Turn parser tracing on by giving a stream to which to write the trace -** and a prompt to preface each trace message. Tracing is turned off -** by making either argument NULL -** -** Inputs: -**
    -**
  • A FILE* to which trace output should be written. -** If NULL, then tracing is turned off. -**
  • A prefix string written at the beginning of every -** line of trace output. If NULL, then tracing is -** turned off. -**
-** -** Outputs: -** None. -*/ -void ParseTrace(FILE *TraceFILE, char *zTracePrompt); -void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ - yyTraceFILE = TraceFILE; - yyTracePrompt = zTracePrompt; - if( yyTraceFILE==0 ) yyTracePrompt = 0; - else if( yyTracePrompt==0 ) yyTraceFILE = 0; -} -#endif /* NDEBUG */ - -#ifndef NDEBUG -/* For tracing shifts, the names of all terminals and nonterminals -** are required. The following table supplies these names */ -static const char *const yyTokenName[] = { -%% -}; -#endif /* NDEBUG */ - -#ifndef NDEBUG -/* For tracing reduce actions, the names of all rules are required. -*/ -static const char *const yyRuleName[] = { -%% -}; -#endif /* NDEBUG */ - - -#if YYSTACKDEPTH<=0 -/* -** Try to increase the size of the parser stack. -*/ -static void yyGrowStack(yyParser *p){ - int newSize; - yyStackEntry *pNew; - - newSize = p->yystksz*2 + 100; - pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); - if( pNew ){ - p->yystack = pNew; - p->yystksz = newSize; -#ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", - yyTracePrompt, p->yystksz); - } -#endif - } -} -#endif - -/* -** This function allocates a new parser. -** The only argument is a pointer to a function which works like -** malloc. -** -** Inputs: -** A pointer to the function used to allocate memory. -** -** Outputs: -** A pointer to a parser. This pointer is used in subsequent calls -** to Parse and ParseFree. -*/ -void *ParseAlloc(void *(*mallocProc)(size_t)){ - yyParser *pParser; - pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); - if( pParser ){ - pParser->yyidx = -1; -#ifdef YYTRACKMAXSTACKDEPTH - pParser->yyidxMax = 0; -#endif -#if YYSTACKDEPTH<=0 - pParser->yystack = NULL; - pParser->yystksz = 0; - yyGrowStack(pParser); -#endif - } - return pParser; -} - -/* The following function deletes the value associated with a -** symbol. The symbol can be either a terminal or nonterminal. -** "yymajor" is the symbol code, and "yypminor" is a pointer to -** the value. -*/ -static void yy_destructor( - yyParser *yypParser, /* The parser */ - YYCODETYPE yymajor, /* Type code for object to destroy */ - YYMINORTYPE *yypminor /* The object to be destroyed */ -){ - ParseARG_FETCH; - switch( yymajor ){ - /* Here is inserted the actions which take place when a - ** terminal or non-terminal is destroyed. This can happen - ** when the symbol is popped from the stack during a - ** reduce or during error processing or when a parser is - ** being destroyed before it is finished parsing. - ** - ** Note: during a reduce, the only symbols destroyed are those - ** which appear on the RHS of the rule, but which are not used - ** inside the C code. - */ -%% - default: break; /* If no destructor action specified: do nothing */ - } -} - -/* -** Pop the parser's stack once. -** -** If there is a destructor routine associated with the token which -** is popped from the stack, then call it. -** -** Return the major token number for the symbol popped. -*/ -static int yy_pop_parser_stack(yyParser *pParser){ - YYCODETYPE yymajor; - yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; - - if( pParser->yyidx<0 ) return 0; -#ifndef NDEBUG - if( yyTraceFILE && pParser->yyidx>=0 ){ - fprintf(yyTraceFILE,"%sPopping %s\n", - yyTracePrompt, - yyTokenName[yytos->major]); - } -#endif - yymajor = yytos->major; - yy_destructor(pParser, yymajor, &yytos->minor); - pParser->yyidx--; - return yymajor; -} - -/* -** Deallocate and destroy a parser. Destructors are all called for -** all stack elements before shutting the parser down. -** -** Inputs: -**
    -**
  • A pointer to the parser. This should be a pointer -** obtained from ParseAlloc. -**
  • A pointer to a function used to reclaim memory obtained -** from malloc. -**
-*/ -void ParseFree( - void *p, /* The parser to be deleted */ - void (*freeProc)(void*) /* Function used to reclaim memory */ -){ - yyParser *pParser = (yyParser*)p; - if( pParser==0 ) return; - while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); -#if YYSTACKDEPTH<=0 - free(pParser->yystack); -#endif - (*freeProc)((void*)pParser); -} - -/* -** Return the peak depth of the stack for a parser. -*/ -#ifdef YYTRACKMAXSTACKDEPTH -int ParseStackPeak(void *p){ - yyParser *pParser = (yyParser*)p; - return pParser->yyidxMax; -} -#endif - -/* -** Find the appropriate action for a parser given the terminal -** look-ahead token iLookAhead. -** -** If the look-ahead token is YYNOCODE, then check to see if the action is -** independent of the look-ahead. If it is, return the action, otherwise -** return YY_NO_ACTION. -*/ -static int yy_find_shift_action( - yyParser *pParser, /* The parser */ - YYCODETYPE iLookAhead /* The look-ahead token */ -){ - int i; - int stateno = pParser->yystack[pParser->yyidx].stateno; - - if( stateno>YY_SHIFT_COUNT - || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ - return yy_default[stateno]; - } - assert( iLookAhead!=YYNOCODE ); - i += iLookAhead; - if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ - if( iLookAhead>0 ){ -#ifdef YYFALLBACK - YYCODETYPE iFallback; /* Fallback token */ - if( iLookAhead %s\n", - yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); - } -#endif - return yy_find_shift_action(pParser, iFallback); - } -#endif -#ifdef YYWILDCARD - { - int j = i - iLookAhead + YYWILDCARD; - if( -#if YY_SHIFT_MIN+YYWILDCARD<0 - j>=0 && -#endif -#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT - j %s\n", - yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); - } -#endif /* NDEBUG */ - return yy_action[j]; - } - } -#endif /* YYWILDCARD */ - } - return yy_default[stateno]; - }else{ - return yy_action[i]; - } -} - -/* -** Find the appropriate action for a parser given the non-terminal -** look-ahead token iLookAhead. -** -** If the look-ahead token is YYNOCODE, then check to see if the action is -** independent of the look-ahead. If it is, return the action, otherwise -** return YY_NO_ACTION. -*/ -static int yy_find_reduce_action( - int stateno, /* Current state number */ - YYCODETYPE iLookAhead /* The look-ahead token */ -){ - int i; -#ifdef YYERRORSYMBOL - if( stateno>YY_REDUCE_COUNT ){ - return yy_default[stateno]; - } -#else - assert( stateno<=YY_REDUCE_COUNT ); -#endif - i = yy_reduce_ofst[stateno]; - assert( i!=YY_REDUCE_USE_DFLT ); - assert( iLookAhead!=YYNOCODE ); - i += iLookAhead; -#ifdef YYERRORSYMBOL - if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ - return yy_default[stateno]; - } -#else - assert( i>=0 && iyyidx--; -#ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); - } -#endif - while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); - /* Here code is inserted which will execute if the parser - ** stack every overflows */ -%% - ParseARG_STORE; /* Suppress warning about unused %extra_argument var */ -} - -/* -** Perform a shift action. -*/ -static void yy_shift( - yyParser *yypParser, /* The parser to be shifted */ - int yyNewState, /* The new state to shift in */ - int yyMajor, /* The major token to shift in */ - YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ -){ - yyStackEntry *yytos; - yypParser->yyidx++; -#ifdef YYTRACKMAXSTACKDEPTH - if( yypParser->yyidx>yypParser->yyidxMax ){ - yypParser->yyidxMax = yypParser->yyidx; - } -#endif -#if YYSTACKDEPTH>0 - if( yypParser->yyidx>=YYSTACKDEPTH ){ - yyStackOverflow(yypParser, yypMinor); - return; - } -#else - if( yypParser->yyidx>=yypParser->yystksz ){ - yyGrowStack(yypParser); - if( yypParser->yyidx>=yypParser->yystksz ){ - yyStackOverflow(yypParser, yypMinor); - return; - } - } -#endif - yytos = &yypParser->yystack[yypParser->yyidx]; - yytos->stateno = (YYACTIONTYPE)yyNewState; - yytos->major = (YYCODETYPE)yyMajor; - yytos->minor = *yypMinor; -#ifndef NDEBUG - if( yyTraceFILE && yypParser->yyidx>0 ){ - int i; - fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); - fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); - for(i=1; i<=yypParser->yyidx; i++) - fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); - fprintf(yyTraceFILE,"\n"); - } -#endif -} - -/* The following table contains information about every rule that -** is used during the reduce. -*/ -static const struct { - YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ - unsigned char nrhs; /* Number of right-hand side symbols in the rule */ -} yyRuleInfo[] = { -%% -}; - -static void yy_accept(yyParser*); /* Forward Declaration */ - -/* -** Perform a reduce action and the shift that must immediately -** follow the reduce. -*/ -static void yy_reduce( - yyParser *yypParser, /* The parser */ - int yyruleno /* Number of the rule by which to reduce */ -){ - int yygoto; /* The next state */ - int yyact; /* The next action */ - YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ - yyStackEntry *yymsp; /* The top of the parser's stack */ - int yysize; /* Amount to pop the stack */ - ParseARG_FETCH; - yymsp = &yypParser->yystack[yypParser->yyidx]; -#ifndef NDEBUG - if( yyTraceFILE && yyruleno>=0 - && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ - fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, - yyRuleName[yyruleno]); - } -#endif /* NDEBUG */ - - /* Silence complaints from purify about yygotominor being uninitialized - ** in some cases when it is copied into the stack after the following - ** switch. yygotominor is uninitialized when a rule reduces that does - ** not set the value of its left-hand side nonterminal. Leaving the - ** value of the nonterminal uninitialized is utterly harmless as long - ** as the value is never used. So really the only thing this code - ** accomplishes is to quieten purify. - ** - ** 2007-01-16: The wireshark project (www.wireshark.org) reports that - ** without this code, their parser segfaults. I'm not sure what there - ** parser is doing to make this happen. This is the second bug report - ** from wireshark this week. Clearly they are stressing Lemon in ways - ** that it has not been previously stressed... (SQLite ticket #2172) - */ - /*memset(&yygotominor, 0, sizeof(yygotominor));*/ - yygotominor = yyzerominor; - - - switch( yyruleno ){ - /* Beginning here are the reduction cases. A typical example - ** follows: - ** case 0: - ** #line - ** { ... } // User supplied code - ** #line - ** break; - */ -%% - }; - yygoto = yyRuleInfo[yyruleno].lhs; - yysize = yyRuleInfo[yyruleno].nrhs; - yypParser->yyidx -= yysize; - yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); - if( yyact < YYNSTATE ){ -#ifdef NDEBUG - /* If we are not debugging and the reduce action popped at least - ** one element off the stack, then we can push the new element back - ** onto the stack here, and skip the stack overflow test in yy_shift(). - ** That gives a significant speed improvement. */ - if( yysize ){ - yypParser->yyidx++; - yymsp -= yysize-1; - yymsp->stateno = (YYACTIONTYPE)yyact; - yymsp->major = (YYCODETYPE)yygoto; - yymsp->minor = yygotominor; - }else -#endif - { - yy_shift(yypParser,yyact,yygoto,&yygotominor); - } - }else{ - assert( yyact == YYNSTATE + YYNRULE + 1 ); - yy_accept(yypParser); - } -} - -/* -** The following code executes when the parse fails -*/ -#ifndef YYNOERRORRECOVERY -static void yy_parse_failed( - yyParser *yypParser /* The parser */ -){ - ParseARG_FETCH; -#ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); - } -#endif - while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); - /* Here code is inserted which will be executed whenever the - ** parser fails */ -%% - ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ -} -#endif /* YYNOERRORRECOVERY */ - -/* -** The following code executes when a syntax error first occurs. -*/ -static void yy_syntax_error( - yyParser *yypParser, /* The parser */ - int yymajor, /* The major type of the error token */ - YYMINORTYPE yyminor /* The minor type of the error token */ -){ - ParseARG_FETCH; -#define TOKEN (yyminor.yy0) -%% - ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ -} - -/* -** The following is executed when the parser accepts -*/ -static void yy_accept( - yyParser *yypParser /* The parser */ -){ - ParseARG_FETCH; -#ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); - } -#endif - while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); - /* Here code is inserted which will be executed whenever the - ** parser accepts */ -%% - ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ -} - -/* The main parser program. -** The first argument is a pointer to a structure obtained from -** "ParseAlloc" which describes the current state of the parser. -** The second argument is the major token number. The third is -** the minor token. The fourth optional argument is whatever the -** user wants (and specified in the grammar) and is available for -** use by the action routines. -** -** Inputs: -**
    -**
  • A pointer to the parser (an opaque structure.) -**
  • The major token number. -**
  • The minor token number. -**
  • An option argument of a grammar-specified type. -**
-** -** Outputs: -** None. -*/ -void Parse( - void *yyp, /* The parser */ - int yymajor, /* The major token code number */ - ParseTOKENTYPE yyminor /* The value for the token */ - ParseARG_PDECL /* Optional %extra_argument parameter */ -){ - YYMINORTYPE yyminorunion; - int yyact; /* The parser action. */ - int yyendofinput; /* True if we are at the end of input */ -#ifdef YYERRORSYMBOL - int yyerrorhit = 0; /* True if yymajor has invoked an error */ -#endif - yyParser *yypParser; /* The parser */ - - /* (re)initialize the parser, if necessary */ - yypParser = (yyParser*)yyp; - if( yypParser->yyidx<0 ){ -#if YYSTACKDEPTH<=0 - if( yypParser->yystksz <=0 ){ - /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ - yyminorunion = yyzerominor; - yyStackOverflow(yypParser, &yyminorunion); - return; - } -#endif - yypParser->yyidx = 0; - yypParser->yyerrcnt = -1; - yypParser->yystack[0].stateno = 0; - yypParser->yystack[0].major = 0; - } - yyminorunion.yy0 = yyminor; - yyendofinput = (yymajor==0); - ParseARG_STORE; - -#ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); - } -#endif - - do{ - yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); - if( yyactyyerrcnt--; - yymajor = YYNOCODE; - }else if( yyact < YYNSTATE + YYNRULE ){ - yy_reduce(yypParser,yyact-YYNSTATE); - }else{ - assert( yyact == YY_ERROR_ACTION ); -#ifdef YYERRORSYMBOL - int yymx; -#endif -#ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); - } -#endif -#ifdef YYERRORSYMBOL - /* A syntax error has occurred. - ** The response to an error depends upon whether or not the - ** grammar defines an error token "ERROR". - ** - ** This is what we do if the grammar does define ERROR: - ** - ** * Call the %syntax_error function. - ** - ** * Begin popping the stack until we enter a state where - ** it is legal to shift the error symbol, then shift - ** the error symbol. - ** - ** * Set the error count to three. - ** - ** * Begin accepting and shifting new tokens. No new error - ** processing will occur until three tokens have been - ** shifted successfully. - ** - */ - if( yypParser->yyerrcnt<0 ){ - yy_syntax_error(yypParser,yymajor,yyminorunion); - } - yymx = yypParser->yystack[yypParser->yyidx].major; - if( yymx==YYERRORSYMBOL || yyerrorhit ){ -#ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sDiscard input token %s\n", - yyTracePrompt,yyTokenName[yymajor]); - } -#endif - yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); - yymajor = YYNOCODE; - }else{ - while( - yypParser->yyidx >= 0 && - yymx != YYERRORSYMBOL && - (yyact = yy_find_reduce_action( - yypParser->yystack[yypParser->yyidx].stateno, - YYERRORSYMBOL)) >= YYNSTATE - ){ - yy_pop_parser_stack(yypParser); - } - if( yypParser->yyidx < 0 || yymajor==0 ){ - yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); - yy_parse_failed(yypParser); - yymajor = YYNOCODE; - }else if( yymx!=YYERRORSYMBOL ){ - YYMINORTYPE u2; - u2.YYERRSYMDT = 0; - yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); - } - } - yypParser->yyerrcnt = 3; - yyerrorhit = 1; -#elif defined(YYNOERRORRECOVERY) - /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to - ** do any kind of error recovery. Instead, simply invoke the syntax - ** error routine and continue going as if nothing had happened. - ** - ** Applications can set this macro (for example inside %include) if - ** they intend to abandon the parse upon the first syntax error seen. - */ - yy_syntax_error(yypParser,yymajor,yyminorunion); - yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); - yymajor = YYNOCODE; - -#else /* YYERRORSYMBOL is not defined */ - /* This is what we do if the grammar does not define ERROR: - ** - ** * Report an error message, and throw away the input token. - ** - ** * If the input token is $, then fail the parse. - ** - ** As before, subsequent error messages are suppressed until - ** three input tokens have been successfully shifted. - */ - if( yypParser->yyerrcnt<=0 ){ - yy_syntax_error(yypParser,yymajor,yyminorunion); - } - yypParser->yyerrcnt = 3; - yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); - if( yyendofinput ){ - yy_parse_failed(yypParser); - } - yymajor = YYNOCODE; -#endif - } - }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); - return; -} diff --git a/modules/luci-base-ucode/src/lib/lmo.c b/modules/luci-base-ucode/src/lib/lmo.c deleted file mode 100644 index da521bc98b..0000000000 --- a/modules/luci-base-ucode/src/lib/lmo.c +++ /dev/null @@ -1,636 +0,0 @@ -/* - * lmo - Lua Machine Objects - Base functions - * - * Copyright (C) 2009-2010 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "lmo.h" -#include "plural_formula.h" - -/* - * Hash function from http://www.azillionmonkeys.com/qed/hash.html - * Copyright (C) 2004-2008 by Paul Hsieh - */ - -uint32_t sfh_hash(const char *data, size_t len, uint32_t init) -{ - uint32_t hash = init, tmp; - int rem; - - if (len <= 0 || data == NULL) return 0; - - rem = len & 3; - len >>= 2; - - /* Main loop */ - for (;len > 0; len--) { - hash += sfh_get16(data); - tmp = (sfh_get16(data+2) << 11) ^ hash; - hash = (hash << 16) ^ tmp; - data += 2*sizeof(uint16_t); - hash += hash >> 11; - } - - /* Handle end cases */ - switch (rem) { - case 3: hash += sfh_get16(data); - hash ^= hash << 16; - hash ^= (signed char)data[sizeof(uint16_t)] << 18; - hash += hash >> 11; - break; - case 2: hash += sfh_get16(data); - hash ^= hash << 11; - hash += hash >> 17; - break; - case 1: hash += (signed char)*data; - hash ^= hash << 10; - hash += hash >> 1; - } - - /* Force "avalanching" of final 127 bits */ - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - - return hash; -} - -uint32_t lmo_canon_hash(const char *str, int len, - const char *ctx, int ctxlen, int plural) -{ - char res[4096]; - char *ptr, *end, prev; - int off; - - if (!str) - return 0; - - ptr = res; - end = res + sizeof(res); - - if (ctx) - { - for (prev = ' ', off = 0; off < ctxlen; prev = *ctx, off++, ctx++) - { - if (ptr >= end) - return 0; - - if (isspace(*ctx)) - { - if (!isspace(prev)) - *ptr++ = ' '; - } - else - { - *ptr++ = *ctx; - } - } - - if ((ptr > res) && isspace(*(ptr-1))) - ptr--; - - if (ptr >= end) - return 0; - - *ptr++ = '\1'; - } - - for (prev = ' ', off = 0; off < len; prev = *str, off++, str++) - { - if (ptr >= end) - return 0; - - if (isspace(*str)) - { - if (!isspace(prev)) - *ptr++ = ' '; - } - else - { - *ptr++ = *str; - } - } - - if ((ptr > res) && isspace(*(ptr-1))) - ptr--; - - if (plural > -1) - { - if (plural >= 100 || ptr + 3 >= end) - return 0; - - ptr += snprintf(ptr, 3, "\2%d", plural); - } - - return sfh_hash(res, ptr - res, ptr - res); -} - -lmo_archive_t * lmo_open(const char *file) -{ - int in = -1; - uint32_t idx_offset = 0; - struct stat s; - - lmo_archive_t *ar = NULL; - - if (stat(file, &s) == -1) - goto err; - - if ((in = open(file, O_RDONLY)) == -1) - goto err; - - if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL) - { - memset(ar, 0, sizeof(*ar)); - - ar->fd = in; - ar->size = s.st_size; - - fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC); - - if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED) - goto err; - - idx_offset = ntohl(*((const uint32_t *) - (ar->mmap + ar->size - sizeof(uint32_t)))); - - if (idx_offset >= ar->size) - goto err; - - ar->index = (lmo_entry_t *)(ar->mmap + idx_offset); - ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t); - ar->end = ar->mmap + ar->size; - - return ar; - } - -err: - if (in > -1) - close(in); - - if (ar != NULL) - { - if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) - munmap(ar->mmap, ar->size); - - free(ar); - } - - return NULL; -} - -void lmo_close(lmo_archive_t *ar) -{ - if (ar != NULL) - { - if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) - munmap(ar->mmap, ar->size); - - close(ar->fd); - free(ar); - - ar = NULL; - } -} - - -lmo_catalog_t *_lmo_catalogs = NULL; -lmo_catalog_t *_lmo_active_catalog = NULL; - -int lmo_load_catalog(const char *lang, const char *dir) -{ - DIR *dh = NULL; - char pattern[16]; - char path[PATH_MAX]; - struct dirent *de = NULL; - - lmo_archive_t *ar = NULL; - lmo_catalog_t *cat = NULL; - - if (!lmo_change_catalog(lang)) - return 0; - - if (!dir || !(dh = opendir(dir))) - goto err; - - if (!(cat = malloc(sizeof(*cat)))) - goto err; - - memset(cat, 0, sizeof(*cat)); - - snprintf(cat->lang, sizeof(cat->lang), "%s", lang); - snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang); - - while ((de = readdir(dh)) != NULL) - { - if (!fnmatch(pattern, de->d_name, 0)) - { - snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); - ar = lmo_open(path); - - if (ar) - { - ar->next = cat->archives; - cat->archives = ar; - } - } - } - - closedir(dh); - - cat->next = _lmo_catalogs; - _lmo_catalogs = cat; - - if (!_lmo_active_catalog) - _lmo_active_catalog = cat; - - return cat->archives ? 0 : -1; - -err: - if (dh) closedir(dh); - if (cat) free(cat); - - return -1; -} - -int lmo_change_catalog(const char *lang) -{ - lmo_catalog_t *cat; - - for (cat = _lmo_catalogs; cat; cat = cat->next) - { - if (!strncmp(cat->lang, lang, sizeof(cat->lang))) - { - _lmo_active_catalog = cat; - return 0; - } - } - - return -1; -} - -static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash) -{ - unsigned int m, l, r; - uint32_t k; - - l = 0; - r = ar->length - 1; - - while (1) - { - m = l + ((r - l) / 2); - - if (r < l) - break; - - k = ntohl(ar->index[m].key_id); - - if (k == hash) - return &ar->index[m]; - - if (k > hash) - { - if (!m) - break; - - r = m - 1; - } - else - { - l = m + 1; - } - } - - return NULL; -} - -void *pluralParseAlloc(void *(*)(size_t)); -void pluralParse(void *, int, int, void *); -void pluralParseFree(void *, void (*)(void *)); - -static int lmo_eval_plural(const char *expr, int len, int val) -{ - struct { int num; int res; } s = { .num = val, .res = -1 }; - const char *p = NULL; - void *pParser = NULL; - int t, n; - char c; - - while (len > 7) { - if (*expr == 'p') { - if (!strncmp(expr, "plural=", 7)) { - p = expr + 7; - len -= 7; - break; - } - } - - expr++; - len--; - } - - if (!p) - goto out; - - pParser = pluralParseAlloc(malloc); - - if (!pParser) - goto out; - - while (len-- > 0) { - c = *p++; - t = -1; - n = 0; - - switch (c) { - case ' ': - case '\t': - continue; - - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - t = T_NUM; - n = c - '0'; - - while (*p >= '0' && *p <= '9') { - n *= 10; - n += *p - '0'; - p++; - } - - break; - - case '=': - if (*p == '=') { - t = T_EQ; - p++; - } - - break; - - case '!': - if (*p == '=') { - t = T_NE; - p++; - } - else { - t = T_NOT; - } - - break; - - case '&': - if (*p == '&') { - t = T_AND; - p++; - } - - break; - - case '|': - if (*p == '|') { - t = T_OR; - p++; - } - - break; - - case '<': - if (*p == '=') { - t = T_LE; - p++; - } - else { - t = T_LT; - } - - break; - - case '>': - if (*p == '=') { - t = T_GE; - p++; - } - else { - t = T_GT; - } - - break; - - case '*': - t = T_MUL; - break; - - case '/': - t = T_DIV; - break; - - case '%': - t = T_MOD; - break; - - case '+': - t = T_ADD; - break; - - case '-': - t = T_SUB; - break; - - case 'n': - t = T_N; - break; - - case '?': - t = T_QMARK; - break; - - case ':': - t = T_COLON; - break; - - case '(': - t = T_LPAREN; - break; - - case ')': - t = T_RPAREN; - break; - - case ';': - case '\n': - case '\0': - t = 0; - break; - } - - /* syntax error */ - if (t < 0) - goto out; - - pluralParse(pParser, t, n, &s); - - /* eof */ - if (t == 0) - break; - } - - pluralParse(pParser, 0, 0, &s); - -out: - pluralParseFree(pParser, free); - - return s.res; -} - -int lmo_translate(const char *key, int keylen, char **out, int *outlen) -{ - return lmo_translate_ctxt(key, keylen, NULL, 0, out, outlen); -} - -int lmo_translate_ctxt(const char *key, int keylen, - const char *ctx, int ctxlen, - char **out, int *outlen) -{ - uint32_t hash; - lmo_entry_t *e; - lmo_archive_t *ar; - - if (!key || !_lmo_active_catalog) - return -2; - - hash = lmo_canon_hash(key, keylen, ctx, ctxlen, -1); - - if (hash > 0) - { - for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) - { - if ((e = lmo_find_entry(ar, hash)) != NULL) - { - *out = ar->mmap + ntohl(e->offset); - *outlen = ntohl(e->length); - return 0; - } - } - } - - return -1; -} - -int lmo_translate_plural(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - char **out, int *outlen) -{ - return lmo_translate_plural_ctxt(n, skey, skeylen, pkey, pkeylen, - NULL, 0, out, outlen); -} - -int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - const char *ctx, int ctxlen, - char **out, int *outlen) -{ - int pid = -1; - uint32_t hash; - lmo_entry_t *e; - lmo_archive_t *ar; - - if (!skey || !pkey || !_lmo_active_catalog) - return -2; - - for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) { - e = lmo_find_entry(ar, 0); - - if (e != NULL) { - pid = lmo_eval_plural(ar->mmap + ntohl(e->offset), ntohl(e->length), n); - break; - } - } - - if (pid == -1) - pid = (n != 1); - - hash = lmo_canon_hash(skey, skeylen, ctx, ctxlen, pid); - - if (hash == 0) - return -1; - - for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) - { - if ((e = lmo_find_entry(ar, hash)) != NULL) - { - *out = ar->mmap + ntohl(e->offset); - *outlen = ntohl(e->length); - return 0; - } - } - - if (n != 1) - { - *out = (char *)pkey; - *outlen = pkeylen; - } - else - { - *out = (char *)skey; - *outlen = skeylen; - } - - return 0; -} - -void lmo_iterate(lmo_iterate_cb_t cb, void *priv) -{ - unsigned int i; - lmo_entry_t *e; - lmo_archive_t *ar; - - if (!_lmo_active_catalog) - return; - - for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) - for (i = 0, e = &ar->index[0]; i < ar->length; e = &ar->index[++i]) - cb(ntohl(e->key_id), ar->mmap + ntohl(e->offset), ntohl(e->length), priv); -} - -void lmo_close_catalog(const char *lang) -{ - lmo_archive_t *ar, *next; - lmo_catalog_t *cat, *prev; - - for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next) - { - if (!strncmp(cat->lang, lang, sizeof(cat->lang))) - { - if (prev) - prev->next = cat->next; - else - _lmo_catalogs = cat->next; - - for (ar = cat->archives; ar; ar = next) - { - next = ar->next; - lmo_close(ar); - } - - free(cat); - break; - } - } -} diff --git a/modules/luci-base-ucode/src/lib/lmo.h b/modules/luci-base-ucode/src/lib/lmo.h deleted file mode 100644 index 744209f62c..0000000000 --- a/modules/luci-base-ucode/src/lib/lmo.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * lmo - Lua Machine Objects - General header - * - * Copyright (C) 2009-2012 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _TEMPLATE_LMO_H_ -#define _TEMPLATE_LMO_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if (defined(__GNUC__) && defined(__i386__)) -#define sfh_get16(d) (*((const uint16_t *) (d))) -#else -#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ - +(uint32_t)(((const uint8_t *)(d))[0]) ) -#endif - -#ifndef __hidden -#define __hidden __attribute__((visibility("hidden"))) -#endif - - -struct lmo_entry { - uint32_t key_id; - uint32_t val_id; - uint32_t offset; - uint32_t length; -} __attribute__((packed)); - -typedef struct lmo_entry lmo_entry_t; - - -struct lmo_archive { - int fd; - int length; - uint32_t size; - lmo_entry_t *index; - char *mmap; - char *end; - struct lmo_archive *next; -}; - -typedef struct lmo_archive lmo_archive_t; - - -struct lmo_catalog { - char lang[6]; - struct lmo_archive *archives; - struct lmo_catalog *next; -}; - -typedef struct lmo_catalog lmo_catalog_t; - -typedef void (*lmo_iterate_cb_t)(uint32_t, const char *, int, void *); - -__hidden uint32_t sfh_hash(const char *data, size_t len, uint32_t init); -__hidden uint32_t lmo_canon_hash(const char *data, int len, - const char *ctx, int ctxlen, int plural); - -__hidden lmo_archive_t * lmo_open(const char *file); -__hidden void lmo_close(lmo_archive_t *ar); - - -__hidden extern lmo_catalog_t *_lmo_catalogs; -__hidden extern lmo_catalog_t *_lmo_active_catalog; - -__hidden int lmo_load_catalog(const char *lang, const char *dir); -__hidden int lmo_change_catalog(const char *lang); -__hidden int lmo_translate(const char *key, int keylen, char **out, int *outlen); -__hidden int lmo_translate_ctxt(const char *key, int keylen, - const char *ctx, int ctxlen, char **out, int *outlen); -__hidden int lmo_translate_plural(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - char **out, int *outlen); -__hidden int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - const char *ctx, int ctxlen, - char **out, int *outlen); -__hidden void lmo_iterate(lmo_iterate_cb_t cb, void *priv); -__hidden void lmo_close_catalog(const char *lang); - -#endif diff --git a/modules/luci-base-ucode/src/lib/luci.c b/modules/luci-base-ucode/src/lib/luci.c deleted file mode 100644 index e6860e727d..0000000000 --- a/modules/luci-base-ucode/src/lib/luci.c +++ /dev/null @@ -1,383 +0,0 @@ -/* - * LuCI low level routines - ucode binding - * - * Copyright (C) 2009-2022 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "lmo.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -/* translation catalog functions */ - -static uc_value_t * -uc_luci_load_catalog(uc_vm_t *vm, size_t nargs) { - uc_value_t *lang = uc_fn_arg(0); - uc_value_t *dir = uc_fn_arg(1); - - if (lang && ucv_type(lang) != UC_STRING) - return NULL; - - if (dir && ucv_type(dir) != UC_STRING) - return NULL; - - return ucv_boolean_new(lmo_load_catalog( - lang ? ucv_string_get(lang) : "en", - ucv_string_get(dir)) == 0); -} - -static uc_value_t * -uc_luci_close_catalog(uc_vm_t *vm, size_t nargs) { - uc_value_t *lang = uc_fn_arg(0); - - if (lang && ucv_type(lang) != UC_STRING) - return NULL; - - lmo_close_catalog(lang ? ucv_string_get(lang) : "en"); - - return ucv_boolean_new(true); -} - -static uc_value_t * -uc_luci_change_catalog(uc_vm_t *vm, size_t nargs) { - uc_value_t *lang = uc_fn_arg(0); - - if (lang && ucv_type(lang) != UC_STRING) - return NULL; - - return ucv_boolean_new(lmo_change_catalog( - lang ? ucv_string_get(lang) : "en") == 0); -} - -static void -uc_luci_get_translations_cb(uint32_t key, const char *val, int len, void *priv) { - uc_vm_t *vm = priv; - - uc_vm_stack_push(vm, ucv_get(uc_vm_stack_peek(vm, 0))); - uc_vm_stack_push(vm, ucv_uint64_new(key)); - uc_vm_stack_push(vm, ucv_string_new_length(val, (size_t)len)); - - if (uc_vm_call(vm, false, 2) == EXCEPTION_NONE) - ucv_put(uc_vm_stack_pop(vm)); -} - -static uc_value_t * -uc_luci_get_translations(uc_vm_t *vm, size_t nargs) { - lmo_iterate(uc_luci_get_translations_cb, vm); - - return ucv_boolean_new(true); -} - -static uc_value_t * -uc_luci_translate(uc_vm_t *vm, size_t nargs) { - uc_value_t *key = uc_fn_arg(0); - uc_value_t *ctx = uc_fn_arg(1); - int trlen; - char *tr; - - if (ucv_type(key) != UC_STRING) - return NULL; - - if (ctx && ucv_type(ctx) != UC_STRING) - return NULL; - - if (lmo_translate_ctxt(ucv_string_get(key), ucv_string_length(key), - ucv_string_get(ctx), ucv_string_length(ctx), - &tr, &trlen) != 0) - return NULL; - - return ucv_string_new_length(tr, (size_t)trlen); -} - -static uc_value_t * -uc_luci_ntranslate(uc_vm_t *vm, size_t nargs) { - uc_value_t *cnt = uc_fn_arg(0); - uc_value_t *skey = uc_fn_arg(1); - uc_value_t *pkey = uc_fn_arg(2); - uc_value_t *ctx = uc_fn_arg(3); - int trlen; - char *tr; - - if (ucv_type(skey) != UC_STRING || ucv_type(pkey) != UC_STRING) - return NULL; - - if (ctx && ucv_type(ctx) != UC_STRING) - return NULL; - - if (lmo_translate_plural_ctxt(ucv_int64_get(cnt), - ucv_string_get(skey), ucv_string_length(skey), - ucv_string_get(pkey), ucv_string_length(pkey), - ucv_string_get(ctx), ucv_string_length(ctx), - &tr, &trlen) != 0) - return NULL; - - return ucv_string_new_length(tr, (size_t)trlen); -} - -static uc_value_t * -uc_luci_hash(uc_vm_t *vm, size_t nargs) { - uc_value_t *key = uc_fn_arg(0); - uc_value_t *init = uc_fn_arg(1); - - if (ucv_type(key) != UC_STRING) - return NULL; - - if (init && ucv_type(init) != UC_INTEGER) - return NULL; - - return ucv_uint64_new(sfh_hash(ucv_string_get(key), ucv_string_length(key), - init ? ucv_uint64_get(init) : ucv_string_length(key))); -} - - -/* user functions */ - -static uc_value_t * -uc_luci_getspnam(uc_vm_t *vm, size_t nargs) { - uc_value_t *name = uc_fn_arg(0), *rv; - struct spwd *s; - - if (ucv_type(name) != UC_STRING) - return NULL; - - s = getspnam(ucv_string_get(name)); - - if (!s) - return NULL; - - rv = ucv_object_new(vm); - - ucv_object_add(rv, "namp", ucv_string_new(s->sp_namp)); - ucv_object_add(rv, "pwdp", ucv_string_new(s->sp_pwdp)); - ucv_object_add(rv, "lstchg", ucv_int64_new(s->sp_lstchg)); - ucv_object_add(rv, "min", ucv_int64_new(s->sp_min)); - ucv_object_add(rv, "max", ucv_int64_new(s->sp_max)); - ucv_object_add(rv, "warn", ucv_int64_new(s->sp_warn)); - ucv_object_add(rv, "inact", ucv_int64_new(s->sp_inact)); - ucv_object_add(rv, "expire", ucv_int64_new(s->sp_expire)); - - return rv; -} - -static uc_value_t * -uc_luci_getpwnam(uc_vm_t *vm, size_t nargs) { - uc_value_t *name = uc_fn_arg(0), *rv; - struct passwd *p; - - if (ucv_type(name) != UC_STRING) - return NULL; - - p = getpwnam(ucv_string_get(name)); - - if (!p) - return NULL; - - rv = ucv_object_new(vm); - - ucv_object_add(rv, "name", ucv_string_new(p->pw_name)); - ucv_object_add(rv, "passwd", ucv_string_new(p->pw_passwd)); - ucv_object_add(rv, "uid", ucv_int64_new(p->pw_uid)); - ucv_object_add(rv, "gid", ucv_int64_new(p->pw_gid)); - ucv_object_add(rv, "gecos", ucv_string_new(p->pw_gecos)); - ucv_object_add(rv, "dir", ucv_string_new(p->pw_dir)); - ucv_object_add(rv, "shell", ucv_string_new(p->pw_shell)); - - return rv; -} - -static uc_value_t * -uc_luci_crypt(uc_vm_t *vm, size_t nargs) { - uc_value_t *phrase = uc_fn_arg(0); - uc_value_t *setting = uc_fn_arg(1); - char *hash; - - if (ucv_type(phrase) != UC_STRING || ucv_type(setting) != UC_STRING) - return NULL; - - errno = 0; - hash = crypt(ucv_string_get(phrase), ucv_string_get(setting)); - - if (hash == NULL || errno != 0) - return NULL; - - return ucv_string_new(hash); -} - -static uc_value_t * -uc_luci_getuid(uc_vm_t *vm, size_t nargs) { - return ucv_int64_new(getuid()); -} - -static uc_value_t * -uc_luci_getgid(uc_vm_t *vm, size_t nargs) { - return ucv_int64_new(getgid()); -} - -static uc_value_t * -uc_luci_setuid(uc_vm_t *vm, size_t nargs) { - uc_value_t *uid = uc_fn_arg(0); - - if (ucv_type(uid) != UC_INTEGER) - return NULL; - - return ucv_boolean_new(setuid(ucv_int64_get(uid)) == 0); -} - -static uc_value_t * -uc_luci_setgid(uc_vm_t *vm, size_t nargs) { - uc_value_t *gid = uc_fn_arg(0); - - if (ucv_type(gid) != UC_INTEGER) - return NULL; - - return ucv_boolean_new(setgid(ucv_int64_get(gid)) == 0); -} - - -/* misc functions */ - -static uc_value_t * -uc_luci_kill(uc_vm_t *vm, size_t nargs) { - uc_value_t *pid = uc_fn_arg(0); - uc_value_t *sig = uc_fn_arg(1); - - if (ucv_type(pid) != UC_INTEGER || ucv_type(sig) != UC_INTEGER) - return NULL; - - return ucv_boolean_new(kill(ucv_int64_get(pid), ucv_int64_get(sig)) == 0); -} - -static uc_value_t * -uc_luci_uname(uc_vm_t *vm, size_t nargs) { - struct utsname u; - uc_value_t *rv; - - if (uname(&u) == -1) - return NULL; - - rv = ucv_object_new(vm); - - ucv_object_add(rv, "sysname", ucv_string_new(u.sysname)); - ucv_object_add(rv, "nodename", ucv_string_new(u.nodename)); - ucv_object_add(rv, "release", ucv_string_new(u.release)); - ucv_object_add(rv, "version", ucv_string_new(u.version)); - ucv_object_add(rv, "machine", ucv_string_new(u.machine)); - - return rv; -} - -static uc_value_t * -uc_luci_sysinfo(uc_vm_t *vm, size_t nargs) { - uc_value_t *rv, *loads; - struct sysinfo i; - - if (sysinfo(&i) == -1) - return NULL; - - rv = ucv_object_new(vm); - loads = ucv_array_new_length(vm, 3); - - ucv_array_push(loads, ucv_uint64_new(i.loads[0])); - ucv_array_push(loads, ucv_uint64_new(i.loads[1])); - ucv_array_push(loads, ucv_uint64_new(i.loads[2])); - - ucv_object_add(rv, "uptime", ucv_int64_new(i.uptime)); - ucv_object_add(rv, "loads", loads); - ucv_object_add(rv, "totalram", ucv_uint64_new(i.totalram)); - ucv_object_add(rv, "freeram", ucv_uint64_new(i.freeram)); - ucv_object_add(rv, "sharedram", ucv_uint64_new(i.sharedram)); - ucv_object_add(rv, "bufferram", ucv_uint64_new(i.bufferram)); - ucv_object_add(rv, "totalswap", ucv_uint64_new(i.totalswap)); - ucv_object_add(rv, "freeswap", ucv_uint64_new(i.freeswap)); - ucv_object_add(rv, "procs", ucv_uint64_new(i.procs)); - ucv_object_add(rv, "totalhigh", ucv_uint64_new(i.totalhigh)); - ucv_object_add(rv, "freehigh", ucv_uint64_new(i.freehigh)); - ucv_object_add(rv, "mem_unit", ucv_uint64_new(i.mem_unit)); - - return rv; -} - -static uc_value_t * -uc_luci_statvfs(uc_vm_t *vm, size_t nargs) { - uc_value_t *path = uc_fn_arg(0), *rv; - struct statvfs s; - - if (ucv_type(path) != UC_STRING) - return NULL; - - if (statvfs(ucv_string_get(path), &s) == -1) - return NULL; - - rv = ucv_object_new(vm); - - ucv_object_add(rv, "bsize", ucv_uint64_new(s.f_bsize)); - ucv_object_add(rv, "frsize", ucv_uint64_new(s.f_frsize)); - - ucv_object_add(rv, "blocks", ucv_uint64_new(s.f_blocks)); - ucv_object_add(rv, "bfree", ucv_uint64_new(s.f_bfree)); - ucv_object_add(rv, "bavail", ucv_uint64_new(s.f_bavail)); - - ucv_object_add(rv, "files", ucv_uint64_new(s.f_files)); - ucv_object_add(rv, "ffree", ucv_uint64_new(s.f_ffree)); - ucv_object_add(rv, "favail", ucv_uint64_new(s.f_favail)); - - ucv_object_add(rv, "fsid", ucv_uint64_new(s.f_fsid)); - ucv_object_add(rv, "flag", ucv_uint64_new(s.f_flag)); - ucv_object_add(rv, "namemax", ucv_uint64_new(s.f_namemax)); - - return rv; -} - - -static const uc_function_list_t luci_fns[] = { - { "load_catalog", uc_luci_load_catalog }, - { "close_catalog", uc_luci_close_catalog }, - { "change_catalog", uc_luci_change_catalog }, - { "get_translations", uc_luci_get_translations }, - { "translate", uc_luci_translate }, - { "ntranslate", uc_luci_ntranslate }, - { "hash", uc_luci_hash }, - - { "getspnam", uc_luci_getspnam }, - { "getpwnam", uc_luci_getpwnam }, - { "crypt", uc_luci_crypt }, - { "getuid", uc_luci_getuid }, - { "setuid", uc_luci_setuid }, - { "getgid", uc_luci_getgid }, - { "setgid", uc_luci_setgid }, - - { "kill", uc_luci_kill }, - { "uname", uc_luci_uname }, - { "sysinfo", uc_luci_sysinfo }, - { "statvfs", uc_luci_statvfs }, -}; - - -void uc_module_init(uc_vm_t *vm, uc_value_t *scope) -{ - uc_function_list_register(scope, luci_fns); -} diff --git a/modules/luci-base-ucode/src/lib/plural_formula.y b/modules/luci-base-ucode/src/lib/plural_formula.y deleted file mode 100644 index 1623f8b282..0000000000 --- a/modules/luci-base-ucode/src/lib/plural_formula.y +++ /dev/null @@ -1,43 +0,0 @@ -%name pluralParse -%token_type {int} -%extra_argument {struct parse_state *s} - -%right T_QMARK. -%left T_OR. -%left T_AND. -%left T_EQ T_NE. -%left T_LT T_LE T_GT T_GE. -%left T_ADD T_SUB. -%left T_MUL T_DIV T_MOD. -%right T_NOT. -%nonassoc T_COLON T_N T_LPAREN T_RPAREN. - -%include { -#include - -struct parse_state { - int num; - int res; -}; -} - -input ::= expr(A). { s->res = A; } - -expr(A) ::= expr(B) T_QMARK expr(C) T_COLON expr(D). { A = B ? C : D; } -expr(A) ::= expr(B) T_OR expr(C). { A = B || C; } -expr(A) ::= expr(B) T_AND expr(C). { A = B && C; } -expr(A) ::= expr(B) T_EQ expr(C). { A = B == C; } -expr(A) ::= expr(B) T_NE expr(C). { A = B != C; } -expr(A) ::= expr(B) T_LT expr(C). { A = B < C; } -expr(A) ::= expr(B) T_LE expr(C). { A = B <= C; } -expr(A) ::= expr(B) T_GT expr(C). { A = B > C; } -expr(A) ::= expr(B) T_GE expr(C). { A = B >= C; } -expr(A) ::= expr(B) T_ADD expr(C). { A = B + C; } -expr(A) ::= expr(B) T_SUB expr(C). { A = B - C; } -expr(A) ::= expr(B) T_MUL expr(C). { A = B * C; } -expr(A) ::= expr(B) T_DIV expr(C). { A = B / C; } -expr(A) ::= expr(B) T_MOD expr(C). { A = B % C; } -expr(A) ::= T_NOT expr(B). { A = !B; } -expr(A) ::= T_N. { A = s->num; } -expr(A) ::= T_NUM(B). { A = B; } -expr(A) ::= T_LPAREN expr(B) T_RPAREN. { A = B; } diff --git a/modules/luci-base-ucode/ucode/controller/admin/index.uc b/modules/luci-base-ucode/ucode/controller/admin/index.uc deleted file mode 100644 index 16a74abc46..0000000000 --- a/modules/luci-base-ucode/ucode/controller/admin/index.uc +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2022 Jo-Philipp Wich -// Licensed to the public under the Apache License 2.0. - -import { load_catalog, change_catalog, get_translations } from 'luci.core'; - -const ubus_types = [ - null, - 'array', - 'object', - 'string', - null, // INT64 - 'number', - null, // INT16, - 'boolean', - 'double' -]; - - -function ubus_reply(id, data, code, errmsg) { - const reply = { jsonrpc: '2.0', id }; - - if (errmsg) - reply.error = { code, message: errmsg }; - else if (type(code) == 'object') - reply.result = code; - else - reply.result = [ code, data ]; - - return reply; -} - -function ubus_access(sid, obj, fun) { - return (ubus.call('session', 'access', { - ubus_rpc_session: sid, - scope: 'ubus', - object: obj, - function: fun - })?.access == true); -} - -function ubus_request(req) { - if (type(req?.method) != 'string' || req?.jsonrpc != '2.0' || req?.id == null) - return ubus_reply(null, null, -32600, 'Invalid request'); - - if (req.method == 'call') { - if (type(req?.params) != 'array' || length(req.params) < 3) - return ubus_reply(null, null, -32600, 'Invalid parameters'); - - let sid = req.params[0], - obj = req.params[1], - fun = req.params[2], - arg = req.params[3] ?? {}; - - if (type(arg) != 'object' || exists(arg, 'ubus_rpc_session')) - return ubus_reply(req.id, null, -32602, 'Invalid parameters'); - - if (sid == '00000000000000000000000000000000' && ctx.authsession) - sid = ctx.authsession; - - if (!ubus_access(sid, obj, fun)) - return ubus_reply(req.id, null, -32002, 'Access denied'); - - arg.ubus_rpc_session = sid; - - - // clear error - ubus.error(); - - const res = ubus.call(obj, fun, arg); - - return ubus_reply(req.id, res, ubus.error(true) ?? 0); - } - - if (req.method == 'list') { - if (req?.params == null || (type(req.params) == 'array' && length(req.params) == 0)) { - return ubus_reply(req.id, null, ubus.list()); - } - else if (type(req.params) == 'array') { - const rv = {}; - - for (let param in req.params) { - if (type(param) != 'string') - return ubus_reply(req.id, null, -32602, 'Invalid parameters'); - - for (let m, p in ubus.list(param)?.[0]) { - for (let pn, pt in p) { - rv[param] ??= {}; - rv[param][m] ??= {}; - rv[param][m][pn] = ubus_types[pt] ?? 'unknown'; - } - } - } - - return ubus_reply(req.id, null, rv); - } - else { - return ubus_reply(req.id, null, -32602, 'Invalid parameters') - } - } - - return ubus_reply(req.id, null, -32601, 'Method not found') -} - - -return { - action_ubus: function() { - let request; - - try { request = json(http.content()); } - catch { request = null; } - - http.prepare_content('application/json; charset=UTF-8'); - - if (type(request) == 'object') - http.write_json(ubus_request(request)); - else if (type(request) == 'array') - http.write_json(map(request, ubus_request)); - else - http.write_json(ubus_reply(null, null, -32700, 'Parse error')) - }, - - action_translations: function(reqlang) { - if (reqlang != null && reqlang != dispatcher.lang) { - load_catalog(reqlang, '/usr/lib/lua/luci/i18n'); - change_catalog(reqlang); - } - - http.prepare_content('application/javascript; charset=UTF-8'); - http.write('window.TR={'); - - get_translations((key, val) => http.write(sprintf('"%08x":%J,', key, val))); - - http.write('};'); - }, - - action_logout: function() { - const url = dispatcher.build_url(); - - if (ctx.authsession) { - ubus.call('session', 'destroy', { ubus_rpc_session: ctx.authsession }); - - if (http.getenv('HTTPS') == 'on') - http.header('Set-Cookie', `sysauth_https=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=${url}`); - - http.header('Set-Cookie', `sysauth_http=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=${url}`); - } - - http.redirect(url); - }, - - action_menu: function() { - const session = dispatcher.is_authenticated({ methods: [ 'cookie:sysauth_https', 'cookie:sysauth_http' ] }); - const menu = dispatcher.menu_json(session?.acls ?? {}) ?? {}; - - http.prepare_content('application/json; charset=UTF-8'); - http.write_json(menu); - } -}; diff --git a/modules/luci-base-ucode/ucode/controller/admin/uci.uc b/modules/luci-base-ucode/ucode/controller/admin/uci.uc deleted file mode 100644 index c38a42b10b..0000000000 --- a/modules/luci-base-ucode/ucode/controller/admin/uci.uc +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2022 Jo-Philipp Wich -// Licensed to the public under the Apache License 2.0. - -import { STATUS_NO_DATA, STATUS_PERMISSION_DENIED } from 'ubus'; - -let last_ubus_error; - -const ubus_error_map = [ - 200, 'OK', - 400, 'Invalid command', - 400, 'Invalid argument', - 404, 'Method not found', - 404, 'Not found', - 204, 'No data', - 403, 'Permission denied', - 504, 'Timeout', - 500, 'Not supported', - 500, 'Unknown error', - 503, 'Connection failed', - 500, 'Out of memory', - 400, 'Parse error', - 500, 'System error', -]; - -function ubus_call(object, method, args) { - ubus.error(); // clear previous error - - let res = ubus.call(object, method, args); - - last_ubus_error = ubus.error(true); - - return res ?? !last_ubus_error; -} - -function ubus_state_to_http(err) { - let code = ubus_error_map[(err << 1) + 0] ?? 200; - let msg = ubus_error_map[(err << 1) + 1] ?? 'OK'; - - http.status(code, msg); - - if (code != 204) { - http.prepare_content('text/plain'); - http.write(msg); - } -} - -function uci_apply(rollback) { - if (rollback) { - const timeout = +(config?.apply?.rollback ?? 90) || 0; - const success = ubus_call('uci', 'apply', { - ubus_rpc_session: ctx.authsession, - timeout: max(timeout, 90), - rollback: true - }); - - if (success) { - const token = dispatcher.randomid(16); - - ubus.call('session', 'set', { - ubus_rpc_session: '00000000000000000000000000000000', - values: { - rollback: { - token, - session: ctx.authsession, - timeout: time() + timeout - } - } - }); - - return token; - } - - return null; - } - else { - let changes = ubus_call('uci', 'changes', { ubus_rpc_session: ctx.authsession })?.changes; - - for (let config in changes) - if (!ubus_call('uci', 'commit', { ubus_rpc_session: ctx.authsession, config })) - return false; - - return ubus_call('uci', 'apply', { - ubus_rpc_session: ctx.authsession, - rollback: false - }); - } -} - -function uci_confirm(token) { - const data = ubus.call('session', 'get', { - ubus_rpc_session: '00000000000000000000000000000000', - keys: [ 'rollback' ] - })?.values?.rollback; - - if (type(data?.token) != 'string' || type(data?.session) != 'string' || - type(data?.timeout) != 'int' || data.timeout < time()) { - last_ubus_error = STATUS_NO_DATA; - - return false; - } - - if (token != data.token) { - last_ubus_error = STATUS_PERMISSION_DENIED; - - return false; - } - - if (!ubus_call('uci', 'confirm', { ubus_rpc_session: data.session })) - return false; - - ubus_call('session', 'set', { - ubus_rpc_session: '00000000000000000000000000000000', - values: { rollback: {} } - }); - - return true; -} - - -return { - action_apply_rollback: function() { - const token = uci_apply(true); - - if (token) { - http.prepare_content('application/json; charset=UTF-8'); - http.write_json({ token }); - } - else { - ubus_state_to_http(last_ubus_error); - } - }, - - action_apply_unchecked: function() { - uci_apply(false); - ubus_state_to_http(last_ubus_error); - }, - - action_confirm: function() { - uci_confirm(http.formvalue('token')); - ubus_state_to_http(last_ubus_error); - }, - - action_revert: function() { - for (let config in ubus_call('uci', 'changes', { ubus_rpc_session: ctx.authsession })?.changes) - if (!ubus_call('uci', 'revert', { ubus_rpc_session: ctx.authsession, config })) - break; - - ubus_state_to_http(last_ubus_error); - } -}; diff --git a/modules/luci-base-ucode/ucode/dispatcher.uc b/modules/luci-base-ucode/ucode/dispatcher.uc deleted file mode 100644 index 84eff71d3a..0000000000 --- a/modules/luci-base-ucode/ucode/dispatcher.uc +++ /dev/null @@ -1,942 +0,0 @@ -// Copyright 2022 Jo-Philipp Wich -// Licensed to the public under the Apache License 2.0. - -import { open, stat, glob, lsdir, unlink, basename } from 'fs'; -import { striptags, entityencode } from 'html'; -import { connect } from 'ubus'; -import { cursor } from 'uci'; -import { rand } from 'math'; - -import { hash, load_catalog, change_catalog, translate, ntranslate, getuid } from 'luci.core'; -import { revision as luciversion, branch as luciname } from 'luci.version'; -import { default as LuCIRuntime } from 'luci.runtime'; -import { urldecode } from 'luci.http'; - -let ubus = connect(); -let uci = cursor(); - -let indexcache = "/tmp/luci-indexcache"; - -let http, runtime, tree, luabridge; - -function error404(msg) { - http.status(404, 'Not Found'); - - try { - runtime.render('error404', { message: msg ?? 'Not found' }); - } - catch { - http.header('Content-Type', 'text/plain; charset=UTF-8'); - http.write(msg ?? 'Not found'); - } - - return false; -} - -function error500(msg, ex) { - if (!http.eoh) { - http.status(500, 'Internal Server Error'); - http.header('Content-Type', 'text/html; charset=UTF-8'); - } - - try { - runtime.render('error500', { - title: ex?.type ?? 'Runtime exception', - message: replace( - msg, - /(\s)((\/[A-Za-z0-9_.-]+)+:\d+|\[string "[^"]+"\]:\d+)/g, - '$1$2' - ), - exception: ex - }); - } - catch { - http.write('\n'); - http.write(`

${trim(ex)}

\n`); - - if (ex) { - http.write(`

${trim(ex.message)}

\n`); - http.write(`
${trim(ex.stacktrace[0].context)}
\n`); - } - } - - exit(0); -} - -function load_luabridge(optional) { - if (luabridge == null) { - try { - luabridge = require('lua'); - } - catch (ex) { - luabridge = false; - - if (!optional) - error500('No Lua runtime installed'); - } - } - - return luabridge; -} - -function determine_request_language() { - let lang = uci.get('luci', 'main', 'lang') || 'auto'; - - if (lang == 'auto') { - for (let tag in split(http.getenv('HTTP_ACCEPT_LANGUAGE'), ',')) { - tag = split(trim(split(tag, ';')?.[0]), '-'); - - if (tag) { - let cc = tag[1] ? `${tag[0]}_${lc(tag[1])}` : null; - - if (cc && uci.get('luci', 'languages', cc)) { - lang = cc; - break; - } - else if (uci.get('luci', 'languages', tag[0])) { - lang = tag[0]; - break; - } - } - } - } - - if (lang == 'auto') - lang = 'en'; - - if (load_catalog(lang, '/usr/lib/lua/luci/i18n')) - change_catalog(lang); - - return lang; -} - -function determine_version() { - let res = { luciname, luciversion }; - - for (let f = open("/etc/os-release"), l = f?.read?.("line"); l; l = f.read?.("line")) { - let kv = split(l, '=', 2); - - switch (kv[0]) { - case 'NAME': - res.distname = trim(kv[1], '"\' \n'); - break; - - case 'VERSION': - res.distversion = trim(kv[1], '"\' \n'); - break; - - case 'HOME_URL': - res.disturl = trim(kv[1], '"\' \n'); - break; - - case 'BUILD_ID': - res.distrevision = trim(kv[1], '"\' \n'); - break; - } - } - - return res; -} - -function read_jsonfile(path, defval) { - let rv; - - try { - rv = json(open(path, "r")); - } - catch (e) { - rv = defval; - } - - return rv; -} - -function read_cachefile(file, reader) { - let euid = getuid(), - fstat = stat(file), - fuid = fstat?.uid, - perm = fstat?.perm; - - if (euid != fuid || - perm?.group_read || perm?.group_write || perm?.group_exec || - perm?.other_read || perm?.other_write || perm?.other_exec) - return null; - - return reader(file); -} - -function check_fs_depends(spec) { - for (let path, kind in spec) { - if (kind == 'directory') { - if (!length(lsdir(path))) - return false; - } - else if (kind == 'executable') { - let fstat = stat(path); - - if (fstat?.type != 'file' || fstat?.user_exec == false) - return false; - } - else if (kind == 'file') { - let fstat = stat(path); - - if (fstat?.type != 'file') - return false; - } - } - - return true; -} - -function check_uci_depends_options(conf, s, opts) { - if (type(opts) == 'string') { - return (s['.type'] == opts); - } - else if (opts === true) { - for (let option, value in s) - if (ord(option) != 46) - return true; - } - else if (type(opts) == 'object') { - for (let option, value in opts) { - let sval = s[option]; - - if (type(sval) == 'array') { - if (!(value in sval)) - return false; - } - else if (value === true) { - if (sval == null) - return false; - } - else { - if (sval != value) - return false; - } - } - } - - return true; -} - -function check_uci_depends_section(conf, sect) { - for (let section, options in sect) { - let stype = match(section, /^@([A-Za-z0-9_-]+)$/); - - if (stype) { - let found = false; - - uci.load(conf); - uci.foreach(conf, stype[1], (s) => { - if (check_uci_depends_options(conf, s, options)) { - found = true; - return false; - } - }); - - if (!found) - return false; - } - else { - let s = uci.get_all(conf, section); - - if (!s || !check_uci_depends_options(conf, s, options)) - return false; - } - } - - return true; -} - -function check_uci_depends(conf) { - for (let config, values in conf) { - if (values == true) { - let found = false; - - uci.load(config); - uci.foreach(config, null, () => { found = true }); - - if (!found) - return false; - } - else if (type(values) == 'object') { - if (!check_uci_depends_section(config, values)) - return false; - } - } - - return true; -} - -function check_depends(spec) { - if (type(spec?.depends?.fs) in ['array', 'object']) { - let satisfied = false; - let alternatives = (type(spec.depends.fs) == 'array') ? spec.depends.fs : [ spec.depends.fs ]; - - for (let alternative in alternatives) { - if (check_fs_depends(alternative)) { - satisfied = true; - break; - } - } - - if (!satisfied) - return false; - } - - if (type(spec?.depends?.uci) in ['array', 'object']) { - let satisfied = false; - let alternatives = (type(spec.depends.uci) == 'array') ? spec.depends.uci : [ spec.depends.uci ]; - - for (let alternative in alternatives) { - if (check_uci_depends(alternative)) { - satisfied = true; - break; - } - } - - if (!satisfied) - return false; - } - - return true; -} - -function check_acl_depends(require_groups, groups) { - if (length(require_groups)) { - let writable = false; - - for (let group in require_groups) { - let read = ('read' in groups?.[group]); - let write = ('write' in groups?.[group]); - - if (!read && !write) - return null; - - if (write) - writable = true; - } - - return writable; - } - - return true; -} - -function hash_filelist(files) { - let hashval = 0x1b756362; - - for (let file in files) { - let st = stat(file); - - if (st) - hashval = hash(sprintf("%x|%x|%x", st.ino, st.mtime, st.size), hashval); - } - - return hashval; -} - -function build_pagetree() { - let tree = { action: { type: 'firstchild' } }; - - let schema = { - action: 'object', - auth: 'object', - cors: 'bool', - depends: 'object', - order: 'int', - setgroup: 'string', - setuser: 'string', - title: 'string', - wildcard: 'bool', - firstchild_ineligible: 'bool' - }; - - let files = glob('/usr/share/luci/menu.d/*.json', '/usr/lib/lua/luci/controller/*.lua', '/usr/lib/lua/luci/controller/*/*.lua'); - let cachefile; - - if (indexcache) { - cachefile = sprintf('%s.%08x.json', indexcache, hash_filelist(files)); - - let res = read_cachefile(cachefile, read_jsonfile); - - if (res) - return res; - - for (let path in glob(indexcache + '.*.json')) - unlink(path); - } - - for (let file in files) { - let data; - - if (substr(file, -5) == '.json') - data = read_jsonfile(file); - else if (load_luabridge(true)) - data = runtime.call('luci.dispatcher', 'process_lua_controller', file); - else - warn(`Lua controller ${file} present but no Lua runtime installed.\n`); - - if (type(data) == 'object') { - for (let path, spec in data) { - if (type(spec) == 'object') { - let node = tree; - - for (let s in match(path, /[^\/]+/g)) { - if (s[0] == '*') { - node.wildcard = true; - break; - } - - node.children ??= {}; - node.children[s[0]] ??= {}; - node = node.children[s[0]]; - } - - if (node !== tree) { - for (let k, t in schema) - if (type(spec[k]) == t) - node[k] = spec[k]; - - node.satisfied = check_depends(spec); - } - } - } - } - } - - if (cachefile) { - let fd = open(cachefile, 'w', 0600); - - if (fd) { - fd.write(tree); - fd.close(); - } - } - - return tree; -} - -function menu_json(acl) { - tree ??= build_pagetree(); - - return tree; -} - -function ctx_append(ctx, name, node) { - ctx.path ??= []; - push(ctx.path, name); - - ctx.acls ??= []; - push(ctx.acls, ...(node?.depends?.acl || [])); - - ctx.auth = node.auth || ctx.auth; - ctx.cors = node.cors || ctx.cors; - ctx.suid = node.setuser || ctx.suid; - ctx.sgid = node.setgroup || ctx.sgid; - - return ctx; -} - -function session_retrieve(sid, allowed_users) { - let sdat = ubus.call("session", "get", { ubus_rpc_session: sid }); - let sacl = ubus.call("session", "access", { ubus_rpc_session: sid }); - - if (type(sdat?.values?.token) == 'string' && - (!length(allowed_users) || sdat?.values?.username in allowed_users)) { - // uci:set_session_id(sid) - return { - sid, - data: sdat.values, - acls: length(sacl) ? sacl : {} - }; - } - - return null; -} - -function randomid(num_bytes) { - let bytes = []; - - while (num_bytes-- > 0) - push(bytes, sprintf('%02x', rand() % 256)); - - return join('', bytes); -} - -function syslog(prio, msg) { - warn(sprintf("[%s] %s\n", prio, msg)); -} - -function session_setup(user, pass, path) { - let timeout = uci.get('luci', 'sauth', 'sessiontime'); - let login = ubus.call("session", "login", { - username: user, - password: pass, - timeout: timeout ? +timeout : null - }); - - if (type(login?.ubus_rpc_session) == 'string') { - ubus.call("session", "set", { - ubus_rpc_session: login.ubus_rpc_session, - values: { token: randomid(16) } - }); - syslog("info", sprintf("luci: accepted login on /%s for %s from %s", - join('/', path), user || "?", http.getenv("REMOTE_ADDR") || "?")); - - return session_retrieve(login.ubus_rpc_session); - } - - syslog("info", sprintf("luci: failed login on /%s for %s from %s", - join('/', path), user || "?", http.getenv("REMOTE_ADDR") || "?")); -} - -function check_authentication(method) { - let m = match(method, /^([[:alpha:]]+):(.+)$/); - let sid; - - switch (m?.[1]) { - case 'cookie': - sid = http.getcookie(m[2]); - break; - - case 'param': - sid = http.formvalue(m[2]); - break; - - case 'query': - sid = http.formvalue(m[2], true); - break; - } - - return sid ? session_retrieve(sid) : null; -} - -function is_authenticated(auth) { - for (let method in auth?.methods) { - let session = check_authentication(method); - - if (session) - return session; - } - - return null; -} - -function node_weight(node) { - let weight = min(node.order ?? 9999, 9999); - - if (node.auth?.login) - weight += 10000; - - return weight; -} - -function clone(src) { - switch (type(src)) { - case 'array': - return map(src, clone); - - case 'object': - let dest = {}; - - for (let k, v in src) - dest[k] = clone(v); - - return dest; - - default: - return src; - } -} - -function resolve_firstchild(node, session, login_allowed, ctx) { - let candidate, candidate_ctx; - - for (let name, child in node.children) { - if (!child.satisfied) - continue; - - if (!session) - session = is_authenticated(node.auth); - - let cacl = child.depends?.acl; - let login = login_allowed || child.auth?.login; - - if (login || check_acl_depends(cacl, session?.acls?.["access-group"]) != null) { - if (child.title && type(child.action) == "object") { - let child_ctx = ctx_append(clone(ctx), name, child); - if (child.action.type == "firstchild") { - if (!candidate || node_weight(candidate) > node_weight(child)) { - let have_grandchild = resolve_firstchild(child, session, login, child_ctx); - if (have_grandchild) { - candidate = child; - candidate_ctx = child_ctx; - } - } - } - else if (!child.firstchild_ineligible) { - if (!candidate || node_weight(candidate) > node_weight(child)) { - candidate = child; - candidate_ctx = child_ctx; - } - } - } - } - } - - if (!candidate) - return false; - - for (let k, v in candidate_ctx) - ctx[k] = v; - - return true; -} - -function resolve_page(tree, request_path) { - let node = tree; - let login = false; - let session = null; - let ctx = {}; - - for (let i, s in request_path) { - node = node.children?.[s]; - - if (!node?.satisfied) - break; - - ctx_append(ctx, s, node); - - if (!session) - session = is_authenticated(node.auth); - - if (!login && node.auth?.login) - login = true; - - if (node.wildcard) { - ctx.request_args = []; - ctx.request_path = ctx.path ? [ ...ctx.path ] : []; - - while (++i < length(request_path)) { - push(ctx.request_path, request_path[i]); - push(ctx.request_args, request_path[i]); - } - - break; - } - } - - if (node?.action?.type == 'firstchild') - resolve_firstchild(node, session, login, ctx); - - ctx.acls ??= {}; - ctx.path ??= []; - ctx.request_args ??= []; - ctx.request_path ??= request_path ? [ ...request_path ] : []; - - ctx.authsession = session?.sid; - ctx.authtoken = session?.data?.token; - ctx.authuser = session?.data?.username; - ctx.authacl = session?.acls; - - node = tree; - - for (let s in ctx.path) { - node = node.children[s]; - assert(node, "Internal node resolve error"); - } - - return { node, ctx, session }; -} - -function require_post_security(target, args) { - if (target?.type == 'arcombine') - return require_post_security(length(args) ? target?.targets?.[1] : target?.targets?.[0], args); - - if (type(target?.post) == 'object') { - for (let param_name, required_val in target.post) { - let request_val = http.formvalue(param_name); - - if ((type(required_val) == 'string' && request_val != required_val) || - (required_val == true && request_val == null)) - return false; - } - - return true; - } - - return (target?.post == true); -} - -function test_post_security(authtoken) { - if (http.getenv("REQUEST_METHOD") != "POST") { - http.status(405, "Method Not Allowed"); - http.header("Allow", "POST"); - - return false; - } - - if (http.formvalue("token") != authtoken) { - http.status(403, "Forbidden"); - runtime.render("csrftoken"); - - return false; - } - - return true; -} - -function build_url(...path) { - let url = [ http.getenv('SCRIPT_NAME') ?? '' ]; - - for (let p in path) - if (match(p, /^[A-Za-z0-9_%.\/,;-]+$/)) - push(url, '/', p); - - if (length(url) == 1) - push(url, '/'); - - return join('', url); -} - -function lookup(...segments) { - let node = menu_json(); - let path = []; - - for (let segment in segments) - for (let name in split(segment, '/')) - push(path, name); - - for (let name in path) { - node = node.children[name]; - - if (!node) - return null; - - if (node.leaf) - break; - } - - return { node, url: build_url(...path) }; -} - -function rollback_pending() { - const now = time(); - const rv = ubus.call('session', 'get', { - ubus_rpc_session: '00000000000000000000000000000000', - keys: [ 'rollback' ] - }); - - if (type(rv?.values?.rollback?.token) != 'string' || - type(rv?.values?.rollback?.session) != 'string' || - type(rv?.values?.rollback?.timeout) != 'int' || - rv.values.rollback.timeout <= now) - return false; - - return { - remaining: rv.values.rollback.timeout - now, - session: rv.values.rollback.session, - token: rv.values.rollback.token - }; -} - -let dispatch; - -function run_action(request_path, lang, tree, resolved, action) { - switch (action?.type) { - case 'template': - runtime.render(action.path, {}); - break; - - case 'view': - runtime.render('view', { view: action.path }); - break; - - case 'call': - http.write(render(() => { - runtime.call(action.module, action.function, - ...(action.parameters ?? []), - ...resolved.ctx.request_args - ); - })); - break; - - case 'function': - const mod = require(action.module); - - assert(type(mod[action.function]) == 'function', - `Module '${action.module}' does not export function '${action.function}'`); - - http.write(render(() => { - call(mod[action.function], mod, runtime.env, - ...(action.parameters ?? []), - ...resolved.ctx.request_args - ); - })); - break; - - case 'alias': - dispatch(http, [ ...split(action.path, '/'), ...resolved.ctx.request_args ]); - break; - - case 'rewrite': - dispatch(http, [ - ...splice([ ...request_path ], 0, action.remove), - ...split(action.path, '/'), - ...resolved.ctx.request_args - ]); - break; - - case 'firstchild': - if (!length(tree.children)) - error404("No root node was registered, this usually happens if no module was installed.\n" + - "Install luci-mod-admin-full and retry. " + - "If the module is already installed, try removing the /tmp/luci-indexcache file."); - else - error404(`No page is registered at '/${join("/", resolved.ctx.request_path)}'.\n` + - "If this url belongs to an extension, make sure it is properly installed.\n" + - "If the extension was recently installed, try removing the /tmp/luci-indexcache file."); - break; - - default: - error500(`Unhandled action type ${action?.type ?? '?'}`); - } -} - -dispatch = function(_http, path) { - http = _http; - - let version = determine_version(); - let lang = determine_request_language(); - - runtime = LuCIRuntime({ - http, - ubus, - uci, - ctx: {}, - version, - config: { - main: uci.get_all('luci', 'main') ?? {}, - apply: uci.get_all('luci', 'apply') ?? {} - }, - dispatcher: { - rollback_pending, - is_authenticated, - load_luabridge, - lookup, - menu_json, - build_url, - randomid, - error404, - error500, - lang - }, - striptags, - entityencode, - _: (...args) => translate(...args) ?? args[0], - N_: (...args) => ntranslate(...args) ?? (n[0] == 1 ? n[1] : n[2]), - }); - - try { - let menu = menu_json(); - - path ??= map(match(http.getenv('PATH_INFO'), /[^\/]+/g), m => m[0]); - - let resolved = resolve_page(menu, path); - - runtime.env.ctx = resolved.ctx; - runtime.env.node = resolved.node; - - if (length(resolved.ctx.auth)) { - let session = is_authenticated(resolved.ctx.auth); - - if (!session && resolved.ctx.auth.login) { - let user = http.getenv('HTTP_AUTH_USER'); - let pass = http.getenv('HTTP_AUTH_PASS'); - - if (user == null && pass == null) { - user = http.formvalue('luci_username'); - pass = http.formvalue('luci_password'); - } - - if (user != null && pass != null) - session = session_setup(user, pass, resolved.ctx.request_path); - - if (!session) { - resolved.ctx.path = []; - - http.status(403, 'Forbidden'); - http.header('X-LuCI-Login-Required', 'yes'); - - let scope = { duser: 'root', fuser: user }; - - try { - runtime.render(`themes/${basename(runtime.env.media)}/sysauth`, scope); - } - catch (e) { - runtime.render('sysauth', scope); - } - - return; - } - - let cookie_name = (http.getenv('HTTPS') == 'on') ? 'sysauth_https' : 'sysauth_http', - cookie_secure = (http.getenv('HTTPS') == 'on') ? '; secure' : ''; - - http.header('Set-Cookie', `${cookie_name}=${session.sid}; path=${build_url()}; SameSite=strict; HttpOnly${cookie_secure}`); - http.redirect(build_url(...resolved.ctx.request_path)); - - return; - } - - if (!session) { - http.status(403, 'Forbidden'); - http.header('X-LuCI-Login-Required', 'yes'); - - return; - } - - resolved.ctx.authsession ??= session.sid; - resolved.ctx.authtoken ??= session.data?.token; - resolved.ctx.authuser ??= session.data?.username; - resolved.ctx.authacl ??= session.acls; - } - - if (length(resolved.ctx.acls)) { - let perm = check_acl_depends(resolved.ctx.acls, resolved.ctx.authacl?.['access-group']); - - if (perm == null) { - http.status(403, 'Forbidden'); - - return; - } - - if (resolved.node) - resolved.node.readonly = !perm; - } - - let action = resolved.node.action; - - if (action?.type == 'arcombine') - action = length(resolved.ctx.request_args) ? action.targets?.[1] : action.targets?.[0]; - - if (resolved.ctx.cors && http.getenv('REQUEST_METHOD') == 'OPTIONS') { - http.status(200, 'OK'); - http.header('Access-Control-Allow-Origin', http.getenv('HTTP_ORIGIN') ?? '*'); - http.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); - - return; - } - - if (require_post_security(action) && !test_post_security(resolved.ctx.authtoken)) - return; - - run_action(path, lang, menu, resolved, action); - } - catch (ex) { - error500('Unhandled exception during request dispatching', ex); - } -}; - -export default dispatch; diff --git a/modules/luci-base-ucode/ucode/http.uc b/modules/luci-base-ucode/ucode/http.uc deleted file mode 100644 index b464497eac..0000000000 --- a/modules/luci-base-ucode/ucode/http.uc +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright 2022 Jo-Philipp Wich -// Licensed to the public under the Apache License 2.0. - -import { - urlencode as _urlencode, - urldecode as _urldecode, - urlencoded_parser, multipart_parser, header_attribute, - ENCODE_IF_NEEDED, ENCODE_FULL, DECODE_IF_NEEDED, DECODE_PLUS -} from 'lucihttp'; - -import { - error as fserror, - stdin, stdout, mkstemp -} from 'fs'; - -// luci.http module scope -export let HTTP_MAX_CONTENT = 1024*100; // 100 kB maximum content size - -// Decode a mime encoded http message body with multipart/form-data -// Content-Type. Stores all extracted data associated with its parameter name -// in the params table within the given message object. Multiple parameter -// values are stored as tables, ordinary ones as strings. -// If an optional file callback function is given then it is fed with the -// file contents chunk by chunk and only the extracted file name is stored -// within the params table. The callback function will be called subsequently -// with three arguments: -// o Table containing decoded (name, file) and raw (headers) mime header data -// o String value containing a chunk of the file data -// o Boolean which indicates whether the current chunk is the last one (eof) -export function mimedecode_message_body(src, msg, file_cb) { - let len = 0, maxlen = +msg.env.CONTENT_LENGTH; - let header, field, parser; - - parser = multipart_parser(msg.env.CONTENT_TYPE, function(what, buffer, length) { - if (what == parser.PART_INIT) { - field = {}; - } - else if (what == parser.HEADER_NAME) { - header = lc(buffer); - } - else if (what == parser.HEADER_VALUE && header) { - if (lc(header) == 'content-disposition' && - header_attribute(buffer, null) == 'form-data') { - field.name = header_attribute(buffer, 'name'); - field.file = header_attribute(buffer, 'filename'); - field[1] = field.file; - } - - field.headers = field.headers || {}; - field.headers[header] = buffer; - } - else if (what == parser.PART_BEGIN) { - return !field.file; - } - else if (what == parser.PART_DATA && field.name && length > 0) { - if (field.file) { - if (file_cb) { - file_cb(field, buffer, false); - - msg.params[field.name] = msg.params[field.name] || field; - } - else { - if (!field.fd) - field.fd = mkstemp(field.name); - - if (field.fd) { - field.fd.write(buffer); - msg.params[field.name] = msg.params[field.name] || field; - } - } - } - else { - field.value = buffer; - } - } - else if (what == parser.PART_END && field.name) { - if (field.file && msg.params[field.name]) { - if (file_cb) - file_cb(field, '', true); - else if (field.fd) - field.fd.seek(0); - } - else { - let val = msg.params[field.name]; - - if (type(val) == 'array') - push(val, field.value || ''); - else if (val != null) - msg.params[field.name] = [ val, field.value || '' ]; - else - msg.params[field.name] = field.value || ''; - } - - field = null; - } - else if (what == parser.ERROR) { - err = buffer; - } - - return true; - }, HTTP_MAX_CONTENT); - - while (true) { - let chunk = src(); - - len += length(chunk); - - if (maxlen && len > maxlen + 2) - die('Message body size exceeds Content-Length'); - - if (!parser.parse(chunk)) - die(err); - - if (chunk == null) - break; - } -}; - -// Decode an urlencoded http message body with application/x-www-urlencoded -// Content-Type. Stores all extracted data associated with its parameter name -// in the params table within the given message object. Multiple parameter -// values are stored as tables, ordinary ones as strings. -export function urldecode_message_body(src, msg) { - let len = 0, maxlen = +msg.env.CONTENT_LENGTH; - let err, name, value, parser; - - parser = urlencoded_parser(function (what, buffer, length) { - if (what == parser.TUPLE) { - name = null; - value = null; - } - else if (what == parser.NAME) { - name = _urldecode(buffer, DECODE_PLUS); - } - else if (what == parser.VALUE && name) { - let val = msg.params[name]; - - if (type(val) == 'array') - push(val, _urldecode(buffer, DECODE_PLUS) || ''); - else if (val != null) - msg.params[name] = [ val, _urldecode(buffer, DECODE_PLUS) || '' ]; - else - msg.params[name] = _urldecode(buffer, DECODE_PLUS) || ''; - } - else if (what == parser.ERROR) { - err = buffer; - } - - return true; - }, HTTP_MAX_CONTENT); - - while (true) { - let chunk = src(); - - len += length(chunk); - - if (maxlen && len > maxlen + 2) - die('Message body size exceeds Content-Length'); - - if (!parser.parse(chunk)) - die(err); - - if (chunk == null) - break; - } -}; - -// This function will examine the Content-Type within the given message object -// to select the appropriate content decoder. -// Currently the application/x-www-urlencoded and application/form-data -// mime types are supported. If the encountered content encoding can't be -// handled then the whole message body will be stored unaltered as 'content' -// property within the given message object. -export function parse_message_body(src, msg, filecb) { - if (msg.env.CONTENT_LENGTH || msg.env.REQUEST_METHOD == 'POST') { - let ctype = header_attribute(msg.env.CONTENT_TYPE, null); - - // Is it multipart/mime ? - if (ctype == 'multipart/form-data') - return mimedecode_message_body(src, msg, filecb); - - // Is it application/x-www-form-urlencoded ? - else if (ctype == 'application/x-www-form-urlencoded') - return urldecode_message_body(src, msg); - - // Unhandled encoding - // If a file callback is given then feed it chunk by chunk, else - // store whole buffer in message.content - let sink; - - // If we have a file callback then feed it - if (type(filecb) == 'function') { - let meta = { - name: 'raw', - encoding: msg.env.CONTENT_TYPE - }; - - sink = (chunk) => { - if (chunk != null) - return filecb(meta, chunk, false); - else - return filecb(meta, null, true); - }; - } - - // ... else append to .content - else { - let chunks = [], len = 0; - - sink = (chunk) => { - len += length(chunk); - - if (len > HTTP_MAX_CONTENT) - die('POST data exceeds maximum allowed length'); - - if (chunk != null) { - push(chunks, chunk); - } - else { - msg.content = join('', chunks); - msg.content_length = len; - } - }; - } - - // Pump data... - while (true) { - let chunk = src(); - - sink(chunk); - - if (chunk == null) - break; - } - - return true; - } - - return false; -}; - -export function build_querystring(q) { - let s = []; - - for (let k, v in q) { - push(s, - length(s) ? '&' : '?', - _urlencode(k, ENCODE_IF_NEEDED | ENCODE_FULL) || k, - '=', - _urlencode(v, ENCODE_IF_NEEDED | ENCODE_FULL) || v - ); - } - - return join('', s); -}; - -export function urlencode(value) { - if (value == null) - return null; - - value = '' + value; - - return _urlencode(value, ENCODE_IF_NEEDED | ENCODE_FULL) || value; -}; - -export function urldecode(value, decode_plus) { - if (value == null) - return null; - - value = '' + value; - - return _urldecode(value, DECODE_IF_NEEDED | (decode_plus ? DECODE_PLUS : 0)) || value; -}; - -// Extract and split urlencoded data pairs, separated bei either "&" or ";" -// from given url or string. Returns a table with urldecoded values. -// Simple parameters are stored as string values associated with the parameter -// name within the table. Parameters with multiple values are stored as array -// containing the corresponding values. -export function urldecode_params(url, tbl) { - let parser, name, value; - let params = tbl || {}; - - parser = urlencoded_parser(function(what, buffer, length) { - if (what == parser.TUPLE) { - name = null; - value = null; - } - else if (what == parser.NAME) { - name = _urldecode(buffer); - } - else if (what == parser.VALUE && name) { - params[name] = _urldecode(buffer) || ''; - } - - return true; - }); - - if (parser) { - let m = match(('' + (url || '')), /[^?]*$/); - - parser.parse(m ? m[0] : ''); - parser.parse(null); - } - - return params; -}; - -// Encode each key-value-pair in given table to x-www-urlencoded format, -// separated by '&'. Tables are encoded as parameters with multiple values by -// repeating the parameter name with each value. -export function urlencode_params(tbl) { - let enc = []; - - for (let k, v in tbl) { - if (type(v) == 'array') { - for (let v2 in v) { - if (length(enc)) - push(enc, '&'); - - push(enc, - _urlencode(k), - '=', - _urlencode('' + v2)); - } - } - else { - if (length(enc)) - push(enc, '&'); - - push(enc, - _urlencode(k), - '=', - _urlencode('' + v)); - } - } - - return join(enc, ''); -}; - - -// Default IO routines suitable for CGI invocation -let avail_len = +getenv('CONTENT_LENGTH'); - -const default_source = () => { - let rlen = min(avail_len, 4096); - - if (rlen == 0) { - stdin.close(); - - return null; - } - - let chunk = stdin.read(rlen); - - if (chunk == null) - die(`Input read error: ${fserror()}`); - - avail_len -= length(chunk); - - return chunk; -}; - -const default_sink = (...chunks) => { - for (let chunk in chunks) - stdout.write(chunk); - - stdout.flush(); -}; - -const Class = { - formvalue: function(name, noparse) { - if (!noparse && !this.parsed_input) - this._parse_input(); - - if (name != null) - return this.message.params[name]; - else - return this.message.params; - }, - - formvaluetable: function(prefix) { - let vals = {}; - - prefix = (prefix || '') + '.'; - - if (!this.parsed_input) - this._parse_input(); - - for (let k, v in this.message.params) - if (index(k, prefix) == 0) - vals[substr(k, length(prefix))] = '' + v; - - return vals; - }, - - content: function() { - if (!this.parsed_input) - this._parse_input(); - - return this.message.content; - }, - - getcookie: function(name) { - return header_attribute(`cookie; ${this.getenv('HTTP_COOKIE') ?? ''}`, name); - }, - - getenv: function(name) { - if (name != null) - return this.message.env[name]; - else - return this.message.env; - }, - - setfilehandler: function(callback) { - if (type(callback) == 'resource' && type(callback.call) == 'function') - this.filehandler = (...args) => callback.call(...args); - else if (type(callback) == 'function') - this.filehandler = callback; - else - die('Invalid callback argument for setfilehandler()'); - - if (!this.parsed_input) - return; - - // If input has already been parsed then uploads are stored as unlinked - // temporary files pointed to by open file handles in the parameter - // value table. Loop all params, and invoke the file callback for any - // param with an open file handle. - for (let name, value in this.message.params) { - while (value?.fd) { - let data = value.fd.read(1024); - let eof = (data == null || data == ''); - - callback(value, data, eof); - - if (eof) { - value.fd.close(); - value.fd = null; - } - } - } - }, - - _parse_input: function() { - parse_message_body( - this.input, - this.message, - this.filehandler - ); - - this.parsed_input = true; - }, - - close: function() { - this.write_headers(); - this.closed = true; - }, - - header: function(key, value) { - this.headers ??= {}; - this.headers[lc(key)] = value; - }, - - prepare_content: function(mime) { - if (!this.headers?.['content-type']) { - if (mime == 'application/xhtml+xml') { - if (index(this.getenv('HTTP_ACCEPT'), mime) == -1) { - mime = 'text/html; charset=UTF-8'; - this.header('Vary', 'Accept'); - } - } - - this.header('Content-Type', mime); - } - }, - - status: function(code, message) { - this.status_code = code ?? 200; - this.status_message = message ?? 'OK'; - }, - - write_headers: function() { - if (this.eoh) - return; - - if (!this.status_code) - this.status(); - - if (!this.headers?.['content-type']) - this.header('Content-Type', 'text/html; charset=UTF-8'); - - if (!this.headers?.['cache-control']) { - this.header('Cache-Control', 'no-cache'); - this.header('Expires', '0'); - } - - if (!this.headers?.['x-frame-options']) - this.header('X-Frame-Options', 'SAMEORIGIN'); - - if (!this.headers?.['x-xss-protection']) - this.header('X-XSS-Protection', '1; mode=block'); - - if (!this.headers?.['x-content-type-options']) - this.header('X-Content-Type-Options', 'nosniff'); - - this.output('Status: '); - this.output(this.status_code); - this.output(' '); - this.output(this.status_message); - this.output('\r\n'); - - for (let k, v in this.headers) { - this.output(k); - this.output(': '); - this.output(v); - this.output('\r\n'); - } - - this.output('\r\n'); - - this.eoh = true; - }, - - // If the content chunk is nil this function will automatically invoke close. - write: function(content) { - if (content != null) { - this.write_headers(); - this.output(content); - - return true; - } - else { - this.close(); - } - }, - - redirect: function(url) { - this.status(302, 'Found'); - this.header('Location', url ?? '/'); - this.close(); - }, - - write_json: function(value) { - this.write(sprintf('%.J', value)); - }, - - urlencode, - urlencode_params, - - urldecode, - urldecode_params, - - build_querystring -}; - -export default function(env, sourcein, sinkout) { - return proto({ - input: sourcein ?? default_source, - output: sinkout ?? default_sink, - - // File handler nil by default to let .content() work - file: null, - - // HTTP-Message table - message: { - env, - headers: {}, - params: urldecode_params(env?.QUERY_STRING ?? '') - }, - - parsed_input: false - }, Class); -}; diff --git a/modules/luci-base-ucode/ucode/runtime.uc b/modules/luci-base-ucode/ucode/runtime.uc deleted file mode 100644 index ee0756efc3..0000000000 --- a/modules/luci-base-ucode/ucode/runtime.uc +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2022 Jo-Philipp Wich -// Licensed to the public under the Apache License 2.0. - -import { access, basename } from 'fs'; -import { cursor } from 'uci'; - -const template_directory = '/usr/share/ucode/luci/template'; - -function cut_message(msg) { - return trim(replace(msg, /\n--\n.*$/, '')); -} - -function format_nested_exception(ex) { - let msg = replace(cut_message(ex.message), /(\n+( \|[^\n]*(\n|$))+)/, (m, m1) => { - m1 = replace(m1, /(^|\n) \| ?/g, '$1'); - m = match(m1, /^(.+?)\n(In.*line \d+, byte \d+:.+)$/); - - return ` -
-
${cut_message(m ? m[1] : m1)}
- ${m ? `
${trim(m[2])}
` : ''} -
- `; - }); - - return ` -
-
${cut_message(msg)}
-
${trim(ex.stacktrace[0].context)}
-
- `; -} - -function format_lua_exception(ex) { - let m = match(ex.message, /^(.+)\nstack traceback:\n(.+)$/); - - return ` -
-
${cut_message(m ? m[1] : ex.message)}
-
${m ? trim(replace(m[2], /(^|\n)\t/g, '$1')) : ex.stacktrace[0].context}
-
- `; -} - -const Class = { - init_lua: function() { - if (!this.L) { - this.L = this.env.dispatcher.load_luabridge().create(); - this.L.set('L', proto({ write: print }, this.env)); - this.L.eval('package.path = "/usr/lib/lua/luci/ucodebridge/?.lua;" .. package.path'); - this.L.invoke('require', 'luci.ucodebridge'); - - this.env.lua_active = true; - } - - return this.L; - }, - - render_ucode: function(path, scope) { - let tmplfunc = loadfile(path, { raw_mode: false }); - call(tmplfunc, null, scope ?? {}); - }, - - render_lua: function(path, scope) { - let vm = this.init_lua(); - let render = vm.get('_G', 'luci', 'ucodebridge', 'render'); - - render.call(path, scope ?? {}); - }, - - trycompile: function(path) { - let ucode_path = `${template_directory}/${path}.ut`; - - if (access(ucode_path)) { - try { - loadfile(ucode_path, { raw_mode: false }); - } - catch (ucode_err) { - return `Unable to compile '${path}' as ucode template: ${format_nested_exception(ucode_err)}`; - } - } - else { - try { - let vm = this.init_lua(); - let compile = vm.get('_G', 'luci', 'ucodebridge', 'compile'); - - compile.call(path); - } - catch (lua_err) { - return `Unable to compile '${path}' as Lua template: ${format_lua_exception(lua_err)}`; - } - } - - return true; - }, - - render_any: function(path, scope) { - let ucode_path = `${template_directory}/${path}.ut`; - - scope = proto(scope ?? {}, this.scopes[-1]); - - push(this.scopes, scope); - - try { - if (access(ucode_path)) - this.render_ucode(ucode_path, scope); - else - this.render_lua(path, scope); - } - catch (ex) { - pop(this.scopes); - die(ex); - } - - pop(this.scopes); - }, - - render: function(path, scope) { - let self = this; - this.env.http.write(render(() => self.render_any(path, scope))); - }, - - call: function(modname, method, ...args) { - let vm = this.init_lua(); - let lcall = vm.get('_G', 'luci', 'ucodebridge', 'call'); - - return lcall.call(modname, method, ...args); - } -}; - -export default function(env) { - const self = proto({ env: env ??= {}, scopes: [ proto(env, global) ], global }, Class); - const uci = cursor(); - - // determine theme - let media = uci.get('luci', 'main', 'mediaurlbase'); - let status = self.trycompile(`themes/${basename(media)}/header`); - - if (status !== true) { - media = null; - - for (let k, v in uci.get_all('luci', 'themes')) { - if (substr(k, 0, 1) != '.') { - status = self.trycompile(`themes/${basename(v)}/header`); - - if (status === true) { - media = v; - break; - } - } - } - - if (!media) - error500(`Unable to render any theme header template, last error was:\n${status}`); - } - - self.env.media = media; - self.env.theme = basename(media); - self.env.resource = uci.get('luci', 'main', 'resourcebase'); - self.env.include = (...args) => self.render_any(...args); - - return self; -}; diff --git a/modules/luci-base-ucode/ucode/sys.uc b/modules/luci-base-ucode/ucode/sys.uc deleted file mode 100644 index d4db91a9b9..0000000000 --- a/modules/luci-base-ucode/ucode/sys.uc +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2022 Jo-Philipp Wich -// Licensed to the public under the Apache License 2.0. - -import { basename, readlink, readfile, open, popen, stat, glob } from 'fs'; - -export function process_list() { - const top = popen('/bin/busybox top -bn1'); - let line, list = []; - - for (let line = top.read('line'); length(line); line = top.read('line')) { - let m = match(trim(line), /^([0-9]+) +([0-9]+) +(.+) +([RSDZTWI][ { - const s = stat(path); - - return s?.type == 'file' && s?.perm?.user_exec; - }), basename); -}; - -export function init_index(name) { - const src = readfile(`/etc/init.d/${basename(name)}`, 1024); - const idx = []; - - for (let m in match(src, /^[[:space:]]*(START|STOP)=('[0-9][0-9]'|"[0-9][0-9]"|[0-9][0-9])[[:space:]]*$/gs)) { - switch (m[1]) { - case 'START': idx[0] = +trim(m[2], '"\''); break; - case 'STOP': idx[1] = +trim(m[2], '"\''); break; - } - } - - return length(idx) ? idx : null; -}; - -export function init_enabled(name) { - for (let path in glob(`/etc/rc.d/[SK][0-9][0-9]${basename(name)}`)) { - const ln = readlink(path); - const s1 = stat(index(ln, '/') == 0 ? ln : `/etc/rc.d/${ln}`); - const s2 = stat(`/etc/init.d/${basename(name)}`); - - if (s1?.inode == s2?.inode && s1?.type == 'file' && s1?.perm?.user_exec) - return true; - } - - return false; -}; - -export function init_action(name, action) { - const s = stat(`/etc/init.d/${basename(name)}`); - - if (s?.type != 'file' || s?.user_exec == false) - return false; - - return system(`env -i /etc/init.d/${basename(name)} ${action} >/dev/null`); -}; diff --git a/modules/luci-base-ucode/ucode/template/csrftoken.ut b/modules/luci-base-ucode/ucode/template/csrftoken.ut deleted file mode 100644 index 4e96eebe90..0000000000 --- a/modules/luci-base-ucode/ucode/template/csrftoken.ut +++ /dev/null @@ -1,24 +0,0 @@ -{# - Copyright 2015-2022 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --#} - -{% include('header') %} - -

{{ _('Form token mismatch') }}

-
- -

{{ _('The submitted security token is invalid or already expired!') }}

- -

{{ _(` - In order to prevent unauthorized access to the system, your request has - been blocked. Click "Continue »" below to return to the previous page. -`) }}

- -
- -

- Continue » -

- -{% include('footer') %} diff --git a/modules/luci-base-ucode/ucode/template/error404.ut b/modules/luci-base-ucode/ucode/template/error404.ut deleted file mode 100644 index 90c3d3784b..0000000000 --- a/modules/luci-base-ucode/ucode/template/error404.ut +++ /dev/null @@ -1,14 +0,0 @@ -{# - Copyright 2008 Steven Barth - Copyright 2008-2022 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --#} - -{% include('header') %} - -

404 {{ _('Not Found') }}

-

{{ _('Sorry, the object you requested was not found.') }}

-

{{ message }}

-{{ _('Unable to dispatch') }}: {{ dispatcher.build_url(...ctx.request_path) }} - -{% include('footer') %} diff --git a/modules/luci-base-ucode/ucode/template/error500.ut b/modules/luci-base-ucode/ucode/template/error500.ut deleted file mode 100644 index 39a0eec678..0000000000 --- a/modules/luci-base-ucode/ucode/template/error500.ut +++ /dev/null @@ -1,67 +0,0 @@ -{# - Copyright 2022 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --#} - - - - -
-

{{ title }}

-
{{ message }}
- - {% if (exception): %} -
-
{{ exception.message }}
-
{{ exception.stacktrace[0].context }}
-
- {% endif %} -
diff --git a/modules/luci-base-ucode/ucode/template/footer.ut b/modules/luci-base-ucode/ucode/template/footer.ut deleted file mode 100644 index 22d4f136f0..0000000000 --- a/modules/luci-base-ucode/ucode/template/footer.ut +++ /dev/null @@ -1,23 +0,0 @@ -{# - Copyright 2022 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --#} - -{% const rollback = dispatcher.rollback_pending() %} -{% if (rollback || trigger_apply || trigger_revert): %} - -{% endif %} - -{% include(`themes/${theme}/footer`) %} - - diff --git a/modules/luci-base-ucode/ucode/template/header.ut b/modules/luci-base-ucode/ucode/template/header.ut deleted file mode 100644 index fb61da5146..0000000000 --- a/modules/luci-base-ucode/ucode/template/header.ut +++ /dev/null @@ -1,32 +0,0 @@ -{# - Copyright 2022 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --#} - -{% - include(`themes/${theme}/header`); --%} - - - - diff --git a/modules/luci-base-ucode/ucode/template/sysauth.ut b/modules/luci-base-ucode/ucode/template/sysauth.ut deleted file mode 100644 index 0fe873d440..0000000000 --- a/modules/luci-base-ucode/ucode/template/sysauth.ut +++ /dev/null @@ -1,74 +0,0 @@ -{# - Copyright 2008 Steven Barth - Copyright 2008-2012 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --#} - -{% include('header') %} - -
- {% if (fuser): %} -
-

{{ _('Invalid username and/or password! Please try again.') }}

-
- {% endif %} - -
-

{{ _('Authorization Required') }}

-
- {{ _('Please enter your username and password.') }} -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
- - -
-
- -{% - let https_ports = uci.get('uhttpd', 'main', 'listen_https') ?? []; - - https_ports = uniq(filter( - map( - (type(https_ports) == 'string') ? split(https_port, /\s+/) : https_ports, - e => +match(e, /\d+$/)?.[0] - ), - p => (p >= 0 && p <= 65535) - )); -%} - - - -{% include('footer') %} diff --git a/modules/luci-base-ucode/ucode/template/view.ut b/modules/luci-base-ucode/ucode/template/view.ut deleted file mode 100644 index 11ac824290..0000000000 --- a/modules/luci-base-ucode/ucode/template/view.ut +++ /dev/null @@ -1,12 +0,0 @@ -{% include('header') %} - -
-
{{ _('Loading view…') }}
- -
- -{% include('footer') %} diff --git a/modules/luci-base-ucode/ucode/uhttpd.uc b/modules/luci-base-ucode/ucode/uhttpd.uc deleted file mode 100644 index df1ecc7865..0000000000 --- a/modules/luci-base-ucode/ucode/uhttpd.uc +++ /dev/null @@ -1,12 +0,0 @@ -{% - -import dispatch from 'luci.dispatcher'; -import request from 'luci.http'; - -global.handle_request = function(env) { - let req = request(env, uhttpd.recv, uhttpd.send); - - dispatch(req); - - req.close(); -}; diff --git a/modules/luci-base-ucode/ucode/zoneinfo.uc b/modules/luci-base-ucode/ucode/zoneinfo.uc deleted file mode 100644 index c5e588dd6a..0000000000 --- a/modules/luci-base-ucode/ucode/zoneinfo.uc +++ /dev/null @@ -1,453 +0,0 @@ -// Autogenerated by zoneinfo2ucode.pl - -export default { - 'Africa/Abidjan': 'GMT0', - 'Africa/Accra': 'GMT0', - 'Africa/Addis Ababa': 'EAT-3', - 'Africa/Algiers': 'CET-1', - 'Africa/Asmara': 'EAT-3', - 'Africa/Bamako': 'GMT0', - 'Africa/Bangui': 'WAT-1', - 'Africa/Banjul': 'GMT0', - 'Africa/Bissau': 'GMT0', - 'Africa/Blantyre': 'CAT-2', - 'Africa/Brazzaville': 'WAT-1', - 'Africa/Bujumbura': 'CAT-2', - 'Africa/Cairo': 'EET-2', - 'Africa/Casablanca': '<+01>-1', - 'Africa/Ceuta': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Africa/Conakry': 'GMT0', - 'Africa/Dakar': 'GMT0', - 'Africa/Dar es Salaam': 'EAT-3', - 'Africa/Djibouti': 'EAT-3', - 'Africa/Douala': 'WAT-1', - 'Africa/El Aaiun': '<+01>-1', - 'Africa/Freetown': 'GMT0', - 'Africa/Gaborone': 'CAT-2', - 'Africa/Harare': 'CAT-2', - 'Africa/Johannesburg': 'SAST-2', - 'Africa/Juba': 'CAT-2', - 'Africa/Kampala': 'EAT-3', - 'Africa/Khartoum': 'CAT-2', - 'Africa/Kigali': 'CAT-2', - 'Africa/Kinshasa': 'WAT-1', - 'Africa/Lagos': 'WAT-1', - 'Africa/Libreville': 'WAT-1', - 'Africa/Lome': 'GMT0', - 'Africa/Luanda': 'WAT-1', - 'Africa/Lubumbashi': 'CAT-2', - 'Africa/Lusaka': 'CAT-2', - 'Africa/Malabo': 'WAT-1', - 'Africa/Maputo': 'CAT-2', - 'Africa/Maseru': 'SAST-2', - 'Africa/Mbabane': 'SAST-2', - 'Africa/Mogadishu': 'EAT-3', - 'Africa/Monrovia': 'GMT0', - 'Africa/Nairobi': 'EAT-3', - 'Africa/Ndjamena': 'WAT-1', - 'Africa/Niamey': 'WAT-1', - 'Africa/Nouakchott': 'GMT0', - 'Africa/Ouagadougou': 'GMT0', - 'Africa/Porto-Novo': 'WAT-1', - 'Africa/Sao Tome': 'GMT0', - 'Africa/Tripoli': 'EET-2', - 'Africa/Tunis': 'CET-1', - 'Africa/Windhoek': 'CAT-2', - 'America/Adak': 'HST10HDT,M3.2.0,M11.1.0', - 'America/Anchorage': 'AKST9AKDT,M3.2.0,M11.1.0', - 'America/Anguilla': 'AST4', - 'America/Antigua': 'AST4', - 'America/Araguaina': '<-03>3', - 'America/Argentina/Buenos Aires': '<-03>3', - 'America/Argentina/Catamarca': '<-03>3', - 'America/Argentina/Cordoba': '<-03>3', - 'America/Argentina/Jujuy': '<-03>3', - 'America/Argentina/La Rioja': '<-03>3', - 'America/Argentina/Mendoza': '<-03>3', - 'America/Argentina/Rio Gallegos': '<-03>3', - 'America/Argentina/Salta': '<-03>3', - 'America/Argentina/San Juan': '<-03>3', - 'America/Argentina/San Luis': '<-03>3', - 'America/Argentina/Tucuman': '<-03>3', - 'America/Argentina/Ushuaia': '<-03>3', - 'America/Aruba': 'AST4', - 'America/Asuncion': '<-04>4<-03>,M10.1.0/0,M3.4.0/0', - 'America/Atikokan': 'EST5', - 'America/Bahia': '<-03>3', - 'America/Bahia Banderas': 'CST6CDT,M4.1.0,M10.5.0', - 'America/Barbados': 'AST4', - 'America/Belem': '<-03>3', - 'America/Belize': 'CST6', - 'America/Blanc-Sablon': 'AST4', - 'America/Boa Vista': '<-04>4', - 'America/Bogota': '<-05>5', - 'America/Boise': 'MST7MDT,M3.2.0,M11.1.0', - 'America/Cambridge Bay': 'MST7MDT,M3.2.0,M11.1.0', - 'America/Campo Grande': '<-04>4', - 'America/Cancun': 'EST5', - 'America/Caracas': '<-04>4', - 'America/Cayenne': '<-03>3', - 'America/Cayman': 'EST5', - 'America/Chicago': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Chihuahua': 'MST7MDT,M4.1.0,M10.5.0', - 'America/Costa Rica': 'CST6', - 'America/Creston': 'MST7', - 'America/Cuiaba': '<-04>4', - 'America/Curacao': 'AST4', - 'America/Danmarkshavn': 'GMT0', - 'America/Dawson': 'MST7', - 'America/Dawson Creek': 'MST7', - 'America/Denver': 'MST7MDT,M3.2.0,M11.1.0', - 'America/Detroit': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Dominica': 'AST4', - 'America/Edmonton': 'MST7MDT,M3.2.0,M11.1.0', - 'America/Eirunepe': '<-05>5', - 'America/El Salvador': 'CST6', - 'America/Fort Nelson': 'MST7', - 'America/Fortaleza': '<-03>3', - 'America/Glace Bay': 'AST4ADT,M3.2.0,M11.1.0', - 'America/Goose Bay': 'AST4ADT,M3.2.0,M11.1.0', - 'America/Grand Turk': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Grenada': 'AST4', - 'America/Guadeloupe': 'AST4', - 'America/Guatemala': 'CST6', - 'America/Guayaquil': '<-05>5', - 'America/Guyana': '<-04>4', - 'America/Halifax': 'AST4ADT,M3.2.0,M11.1.0', - 'America/Havana': 'CST5CDT,M3.2.0/0,M11.1.0/1', - 'America/Hermosillo': 'MST7', - 'America/Indiana/Indianapolis': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Indiana/Knox': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Indiana/Marengo': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Indiana/Petersburg': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Indiana/Tell City': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Indiana/Vevay': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Indiana/Vincennes': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Indiana/Winamac': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Inuvik': 'MST7MDT,M3.2.0,M11.1.0', - 'America/Iqaluit': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Jamaica': 'EST5', - 'America/Juneau': 'AKST9AKDT,M3.2.0,M11.1.0', - 'America/Kentucky/Louisville': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Kentucky/Monticello': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Kralendijk': 'AST4', - 'America/La Paz': '<-04>4', - 'America/Lima': '<-05>5', - 'America/Los Angeles': 'PST8PDT,M3.2.0,M11.1.0', - 'America/Lower Princes': 'AST4', - 'America/Maceio': '<-03>3', - 'America/Managua': 'CST6', - 'America/Manaus': '<-04>4', - 'America/Marigot': 'AST4', - 'America/Martinique': 'AST4', - 'America/Matamoros': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Mazatlan': 'MST7MDT,M4.1.0,M10.5.0', - 'America/Menominee': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Merida': 'CST6CDT,M4.1.0,M10.5.0', - 'America/Metlakatla': 'AKST9AKDT,M3.2.0,M11.1.0', - 'America/Mexico City': 'CST6CDT,M4.1.0,M10.5.0', - 'America/Miquelon': '<-03>3<-02>,M3.2.0,M11.1.0', - 'America/Moncton': 'AST4ADT,M3.2.0,M11.1.0', - 'America/Monterrey': 'CST6CDT,M4.1.0,M10.5.0', - 'America/Montevideo': '<-03>3', - 'America/Montserrat': 'AST4', - 'America/Nassau': 'EST5EDT,M3.2.0,M11.1.0', - 'America/New York': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Nipigon': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Nome': 'AKST9AKDT,M3.2.0,M11.1.0', - 'America/Noronha': '<-02>2', - 'America/North Dakota/Beulah': 'CST6CDT,M3.2.0,M11.1.0', - 'America/North Dakota/Center': 'CST6CDT,M3.2.0,M11.1.0', - 'America/North Dakota/New Salem': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Nuuk': '<-03>3<-02>,M3.5.0/-2,M10.5.0/-1', - 'America/Ojinaga': 'MST7MDT,M3.2.0,M11.1.0', - 'America/Panama': 'EST5', - 'America/Pangnirtung': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Paramaribo': '<-03>3', - 'America/Phoenix': 'MST7', - 'America/Port of Spain': 'AST4', - 'America/Port-au-Prince': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Porto Velho': '<-04>4', - 'America/Puerto Rico': 'AST4', - 'America/Punta Arenas': '<-03>3', - 'America/Rainy River': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Rankin Inlet': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Recife': '<-03>3', - 'America/Regina': 'CST6', - 'America/Resolute': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Rio Branco': '<-05>5', - 'America/Santarem': '<-03>3', - 'America/Santiago': '<-04>4<-03>,M9.1.6/24,M4.1.6/24', - 'America/Santo Domingo': 'AST4', - 'America/Sao Paulo': '<-03>3', - 'America/Scoresbysund': '<-01>1<+00>,M3.5.0/0,M10.5.0/1', - 'America/Sitka': 'AKST9AKDT,M3.2.0,M11.1.0', - 'America/St Barthelemy': 'AST4', - 'America/St Johns': 'NST3:30NDT,M3.2.0,M11.1.0', - 'America/St Kitts': 'AST4', - 'America/St Lucia': 'AST4', - 'America/St Thomas': 'AST4', - 'America/St Vincent': 'AST4', - 'America/Swift Current': 'CST6', - 'America/Tegucigalpa': 'CST6', - 'America/Thule': 'AST4ADT,M3.2.0,M11.1.0', - 'America/Thunder Bay': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Tijuana': 'PST8PDT,M3.2.0,M11.1.0', - 'America/Toronto': 'EST5EDT,M3.2.0,M11.1.0', - 'America/Tortola': 'AST4', - 'America/Vancouver': 'PST8PDT,M3.2.0,M11.1.0', - 'America/Whitehorse': 'MST7', - 'America/Winnipeg': 'CST6CDT,M3.2.0,M11.1.0', - 'America/Yakutat': 'AKST9AKDT,M3.2.0,M11.1.0', - 'America/Yellowknife': 'MST7MDT,M3.2.0,M11.1.0', - 'Antarctica/Casey': '<+11>-11', - 'Antarctica/Davis': '<+07>-7', - 'Antarctica/DumontDUrville': '<+10>-10', - 'Antarctica/Macquarie': 'AEST-10AEDT,M10.1.0,M4.1.0/3', - 'Antarctica/Mawson': '<+05>-5', - 'Antarctica/McMurdo': 'NZST-12NZDT,M9.5.0,M4.1.0/3', - 'Antarctica/Palmer': '<-03>3', - 'Antarctica/Rothera': '<-03>3', - 'Antarctica/Syowa': '<+03>-3', - 'Antarctica/Troll': '<+00>0<+02>-2,M3.5.0/1,M10.5.0/3', - 'Antarctica/Vostok': '<+06>-6', - 'Arctic/Longyearbyen': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Asia/Aden': '<+03>-3', - 'Asia/Almaty': '<+06>-6', - 'Asia/Amman': '<+03>-3', - 'Asia/Anadyr': '<+12>-12', - 'Asia/Aqtau': '<+05>-5', - 'Asia/Aqtobe': '<+05>-5', - 'Asia/Ashgabat': '<+05>-5', - 'Asia/Atyrau': '<+05>-5', - 'Asia/Baghdad': '<+03>-3', - 'Asia/Bahrain': '<+03>-3', - 'Asia/Baku': '<+04>-4', - 'Asia/Bangkok': '<+07>-7', - 'Asia/Barnaul': '<+07>-7', - 'Asia/Beirut': 'EET-2EEST,M3.5.0/0,M10.5.0/0', - 'Asia/Bishkek': '<+06>-6', - 'Asia/Brunei': '<+08>-8', - 'Asia/Chita': '<+09>-9', - 'Asia/Choibalsan': '<+08>-8', - 'Asia/Colombo': '<+0530>-5:30', - 'Asia/Damascus': '<+03>-3', - 'Asia/Dhaka': '<+06>-6', - 'Asia/Dili': '<+09>-9', - 'Asia/Dubai': '<+04>-4', - 'Asia/Dushanbe': '<+05>-5', - 'Asia/Famagusta': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Asia/Gaza': 'EET-2EEST,M3.4.4/50,M10.4.4/50', - 'Asia/Hebron': 'EET-2EEST,M3.4.4/50,M10.4.4/50', - 'Asia/Ho Chi Minh': '<+07>-7', - 'Asia/Hong Kong': 'HKT-8', - 'Asia/Hovd': '<+07>-7', - 'Asia/Irkutsk': '<+08>-8', - 'Asia/Jakarta': 'WIB-7', - 'Asia/Jayapura': 'WIT-9', - 'Asia/Jerusalem': 'IST-2IDT,M3.4.4/26,M10.5.0', - 'Asia/Kabul': '<+0430>-4:30', - 'Asia/Kamchatka': '<+12>-12', - 'Asia/Karachi': 'PKT-5', - 'Asia/Kathmandu': '<+0545>-5:45', - 'Asia/Khandyga': '<+09>-9', - 'Asia/Kolkata': 'IST-5:30', - 'Asia/Krasnoyarsk': '<+07>-7', - 'Asia/Kuala Lumpur': '<+08>-8', - 'Asia/Kuching': '<+08>-8', - 'Asia/Kuwait': '<+03>-3', - 'Asia/Macau': 'CST-8', - 'Asia/Magadan': '<+11>-11', - 'Asia/Makassar': 'WITA-8', - 'Asia/Manila': 'PST-8', - 'Asia/Muscat': '<+04>-4', - 'Asia/Nicosia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Asia/Novokuznetsk': '<+07>-7', - 'Asia/Novosibirsk': '<+07>-7', - 'Asia/Omsk': '<+06>-6', - 'Asia/Oral': '<+05>-5', - 'Asia/Phnom Penh': '<+07>-7', - 'Asia/Pontianak': 'WIB-7', - 'Asia/Pyongyang': 'KST-9', - 'Asia/Qatar': '<+03>-3', - 'Asia/Qostanay': '<+06>-6', - 'Asia/Qyzylorda': '<+05>-5', - 'Asia/Riyadh': '<+03>-3', - 'Asia/Sakhalin': '<+11>-11', - 'Asia/Samarkand': '<+05>-5', - 'Asia/Seoul': 'KST-9', - 'Asia/Shanghai': 'CST-8', - 'Asia/Singapore': '<+08>-8', - 'Asia/Srednekolymsk': '<+11>-11', - 'Asia/Taipei': 'CST-8', - 'Asia/Tashkent': '<+05>-5', - 'Asia/Tbilisi': '<+04>-4', - 'Asia/Tehran': '<+0330>-3:30', - 'Asia/Thimphu': '<+06>-6', - 'Asia/Tokyo': 'JST-9', - 'Asia/Tomsk': '<+07>-7', - 'Asia/Ulaanbaatar': '<+08>-8', - 'Asia/Urumqi': '<+06>-6', - 'Asia/Ust-Nera': '<+10>-10', - 'Asia/Vientiane': '<+07>-7', - 'Asia/Vladivostok': '<+10>-10', - 'Asia/Yakutsk': '<+09>-9', - 'Asia/Yangon': '<+0630>-6:30', - 'Asia/Yekaterinburg': '<+05>-5', - 'Asia/Yerevan': '<+04>-4', - 'Atlantic/Azores': '<-01>1<+00>,M3.5.0/0,M10.5.0/1', - 'Atlantic/Bermuda': 'AST4ADT,M3.2.0,M11.1.0', - 'Atlantic/Canary': 'WET0WEST,M3.5.0/1,M10.5.0', - 'Atlantic/Cape Verde': '<-01>1', - 'Atlantic/Faroe': 'WET0WEST,M3.5.0/1,M10.5.0', - 'Atlantic/Madeira': 'WET0WEST,M3.5.0/1,M10.5.0', - 'Atlantic/Reykjavik': 'GMT0', - 'Atlantic/South Georgia': '<-02>2', - 'Atlantic/St Helena': 'GMT0', - 'Atlantic/Stanley': '<-03>3', - 'Australia/Adelaide': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', - 'Australia/Brisbane': 'AEST-10', - 'Australia/Broken Hill': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', - 'Australia/Darwin': 'ACST-9:30', - 'Australia/Eucla': '<+0845>-8:45', - 'Australia/Hobart': 'AEST-10AEDT,M10.1.0,M4.1.0/3', - 'Australia/Lindeman': 'AEST-10', - 'Australia/Lord Howe': '<+1030>-10:30<+11>-11,M10.1.0,M4.1.0', - 'Australia/Melbourne': 'AEST-10AEDT,M10.1.0,M4.1.0/3', - 'Australia/Perth': 'AWST-8', - 'Australia/Sydney': 'AEST-10AEDT,M10.1.0,M4.1.0/3', - 'Etc/GMT': 'GMT0', - 'Etc/GMT+1': '<-01>1', - 'Etc/GMT+10': '<-10>10', - 'Etc/GMT+11': '<-11>11', - 'Etc/GMT+12': '<-12>12', - 'Etc/GMT+2': '<-02>2', - 'Etc/GMT+3': '<-03>3', - 'Etc/GMT+4': '<-04>4', - 'Etc/GMT+5': '<-05>5', - 'Etc/GMT+6': '<-06>6', - 'Etc/GMT+7': '<-07>7', - 'Etc/GMT+8': '<-08>8', - 'Etc/GMT+9': '<-09>9', - 'Etc/GMT-1': '<+01>-1', - 'Etc/GMT-10': '<+10>-10', - 'Etc/GMT-11': '<+11>-11', - 'Etc/GMT-12': '<+12>-12', - 'Etc/GMT-13': '<+13>-13', - 'Etc/GMT-14': '<+14>-14', - 'Etc/GMT-2': '<+02>-2', - 'Etc/GMT-3': '<+03>-3', - 'Etc/GMT-4': '<+04>-4', - 'Etc/GMT-5': '<+05>-5', - 'Etc/GMT-6': '<+06>-6', - 'Etc/GMT-7': '<+07>-7', - 'Etc/GMT-8': '<+08>-8', - 'Etc/GMT-9': '<+09>-9', - 'Europe/Amsterdam': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Andorra': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Astrakhan': '<+04>-4', - 'Europe/Athens': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Belgrade': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Berlin': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Bratislava': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Brussels': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Bucharest': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Budapest': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Busingen': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Chisinau': 'EET-2EEST,M3.5.0,M10.5.0/3', - 'Europe/Copenhagen': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Dublin': 'IST-1GMT0,M10.5.0,M3.5.0/1', - 'Europe/Gibraltar': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Guernsey': 'GMT0BST,M3.5.0/1,M10.5.0', - 'Europe/Helsinki': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Isle of Man': 'GMT0BST,M3.5.0/1,M10.5.0', - 'Europe/Istanbul': '<+03>-3', - 'Europe/Jersey': 'GMT0BST,M3.5.0/1,M10.5.0', - 'Europe/Kaliningrad': 'EET-2', - 'Europe/Kirov': '<+03>-3', - 'Europe/Kyiv': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Lisbon': 'WET0WEST,M3.5.0/1,M10.5.0', - 'Europe/Ljubljana': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/London': 'GMT0BST,M3.5.0/1,M10.5.0', - 'Europe/Luxembourg': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Madrid': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Malta': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Mariehamn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Minsk': '<+03>-3', - 'Europe/Monaco': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Moscow': 'MSK-3', - 'Europe/Oslo': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Paris': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Podgorica': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Prague': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Riga': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Rome': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Samara': '<+04>-4', - 'Europe/San Marino': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Sarajevo': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Saratov': '<+04>-4', - 'Europe/Simferopol': 'MSK-3', - 'Europe/Skopje': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Sofia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Stockholm': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Tallinn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Tirane': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Ulyanovsk': '<+04>-4', - 'Europe/Vaduz': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Vatican': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Vienna': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Vilnius': 'EET-2EEST,M3.5.0/3,M10.5.0/4', - 'Europe/Volgograd': '<+03>-3', - 'Europe/Warsaw': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Zagreb': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Europe/Zurich': 'CET-1CEST,M3.5.0,M10.5.0/3', - 'Indian/Antananarivo': 'EAT-3', - 'Indian/Chagos': '<+06>-6', - 'Indian/Christmas': '<+07>-7', - 'Indian/Cocos': '<+0630>-6:30', - 'Indian/Comoro': 'EAT-3', - 'Indian/Kerguelen': '<+05>-5', - 'Indian/Mahe': '<+04>-4', - 'Indian/Maldives': '<+05>-5', - 'Indian/Mauritius': '<+04>-4', - 'Indian/Mayotte': 'EAT-3', - 'Indian/Reunion': '<+04>-4', - 'Pacific/Apia': '<+13>-13', - 'Pacific/Auckland': 'NZST-12NZDT,M9.5.0,M4.1.0/3', - 'Pacific/Bougainville': '<+11>-11', - 'Pacific/Chatham': '<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45', - 'Pacific/Chuuk': '<+10>-10', - 'Pacific/Easter': '<-06>6<-05>,M9.1.6/22,M4.1.6/22', - 'Pacific/Efate': '<+11>-11', - 'Pacific/Fakaofo': '<+13>-13', - 'Pacific/Fiji': '<+12>-12<+13>,M11.2.0,M1.2.3/99', - 'Pacific/Funafuti': '<+12>-12', - 'Pacific/Galapagos': '<-06>6', - 'Pacific/Gambier': '<-09>9', - 'Pacific/Guadalcanal': '<+11>-11', - 'Pacific/Guam': 'ChST-10', - 'Pacific/Honolulu': 'HST10', - 'Pacific/Kanton': '<+13>-13', - 'Pacific/Kiritimati': '<+14>-14', - 'Pacific/Kosrae': '<+11>-11', - 'Pacific/Kwajalein': '<+12>-12', - 'Pacific/Majuro': '<+12>-12', - 'Pacific/Marquesas': '<-0930>9:30', - 'Pacific/Midway': 'SST11', - 'Pacific/Nauru': '<+12>-12', - 'Pacific/Niue': '<-11>11', - 'Pacific/Norfolk': '<+11>-11<+12>,M10.1.0,M4.1.0/3', - 'Pacific/Noumea': '<+11>-11', - 'Pacific/Pago Pago': 'SST11', - 'Pacific/Palau': '<+09>-9', - 'Pacific/Pitcairn': '<-08>8', - 'Pacific/Pohnpei': '<+11>-11', - 'Pacific/Port Moresby': '<+10>-10', - 'Pacific/Rarotonga': '<-10>10', - 'Pacific/Saipan': 'ChST-10', - 'Pacific/Tahiti': '<-10>10', - 'Pacific/Tarawa': '<+12>-12', - 'Pacific/Tongatapu': '<+13>-13', - 'Pacific/Wake': '<+12>-12', - 'Pacific/Wallis': '<+12>-12', -}; diff --git a/modules/luci-base/Makefile b/modules/luci-base/Makefile index 6cb2b64092..f85f753320 100644 --- a/modules/luci-base/Makefile +++ b/modules/luci-base/Makefile @@ -1,5 +1,5 @@ # -# Copyright (C) 2008-2015 The LuCI Team +# Copyright (C) 2022 Jo-Philipp Wich # # This is free software, licensed under the Apache License, Version 2.0 . # @@ -11,8 +11,20 @@ PKG_NAME:=luci-base LUCI_TYPE:=mod LUCI_BASENAME:=base -LUCI_TITLE:=LuCI core libraries -LUCI_DEPENDS:=+lua +luci-lib-nixio +luci-lib-ip +rpcd +libubus-lua +luci-lib-jsonc +liblucihttp-lua +luci-lib-base +rpcd-mod-file +rpcd-mod-luci +cgi-io +LUCI_TITLE:=LuCI core runtime +LUCI_DEPENDS:=\ + +rpcd \ + +rpcd-mod-file \ + +rpcd-mod-luci \ + +rpcd-mod-ucode \ + +cgi-io \ + +ucode \ + +ucode-mod-fs \ + +ucode-mod-uci \ + +ucode-mod-ubus \ + +ucode-mod-math \ + +ucode-mod-html \ + +liblucihttp-ucode PKG_LICENSE:=MIT @@ -26,6 +38,20 @@ define Package/luci-base/conffiles /etc/config/ucitrack endef +define Package/luci-base/postinst +#!/bin/sh + +if [ -z "$${PKG_INSTROOT}" ] && [ -f /etc/config/uhttpd ]; then + if ! uci -q get uhttpd.main.ucode_prefix | grep -sq /cgi-bin/luci; then + uci add_list uhttpd.main.ucode_prefix='/cgi-bin/luci=/usr/share/ucode/luci/uhttpd.uc' + uci commit uhttpd + service uhttpd reload + fi +fi + +exit 0 +endef + include ../../luci.mk define Host/Configure diff --git a/modules/luci-base/htdocs/cgi-bin/luci b/modules/luci-base/htdocs/cgi-bin/luci index c5c9847346..442e427d41 100755 --- a/modules/luci-base/htdocs/cgi-bin/luci +++ b/modules/luci-base/htdocs/cgi-bin/luci @@ -1,5 +1,41 @@ -#!/usr/bin/lua -require "luci.cacheloader" -require "luci.sgi.cgi" -luci.dispatcher.indexcache = "/tmp/luci-indexcache" -luci.sgi.cgi.run() +#!/usr/bin/env ucode + +'use strict'; + +import { stdin, stdout } from 'fs'; + +import dispatch from 'luci.dispatcher'; +import request from 'luci.http'; + +const input_bufsize = 4096; +let input_available = +getenv('CONTENT_LENGTH') || 0; + +function read(len) { + if (input_available == 0) { + stdin.close(); + + return null; + } + + let chunk = stdin.read(min(input_available, len ?? input_bufsize, input_bufsize)); + + if (chunk == null) { + input_available = 0; + stdin.close(); + } + else { + input_available -= length(chunk); + } + + return chunk; +} + +function write(data) { + return stdout.write(data); +} + +let req = request(getenv(), read, write); + +dispatch(req); + +req.close(); diff --git a/modules/luci-base/luasrc/cacheloader.lua b/modules/luci-base/luasrc/cacheloader.lua deleted file mode 100644 index 7ef971df8d..0000000000 --- a/modules/luci-base/luasrc/cacheloader.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Copyright 2008 Steven Barth --- Copyright 2008 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local config = require "luci.config" -local ccache = require "luci.ccache" - -module "luci.cacheloader" - -if config.ccache and config.ccache.enable == "1" then - ccache.cache_ondemand() -end diff --git a/modules/luci-base/luasrc/ccache.lua b/modules/luci-base/luasrc/ccache.lua deleted file mode 100644 index d3be7cba6c..0000000000 --- a/modules/luci-base/luasrc/ccache.lua +++ /dev/null @@ -1,76 +0,0 @@ --- Copyright 2008 Steven Barth --- Copyright 2008 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local io = require "io" -local fs = require "nixio.fs" -local util = require "luci.util" -local nixio = require "nixio" -local debug = require "debug" -local string = require "string" -local package = require "package" - -local type, loadfile = type, loadfile - - -module "luci.ccache" - -function cache_ondemand(...) - if debug.getinfo(1, 'S').source ~= "=?" then - cache_enable(...) - end -end - -function cache_enable(cachepath, mode) - cachepath = cachepath or "/tmp/luci-modulecache" - mode = mode or "r--r--r--" - - local loader = package.loaders[2] - local uid = nixio.getuid() - - if not fs.stat(cachepath) then - fs.mkdir(cachepath) - end - - local function _encode_filename(name) - local encoded = "" - for i=1, #name do - encoded = encoded .. ("%2X" % string.byte(name, i)) - end - return encoded - end - - local function _load_sane(file) - local stat = fs.stat(file) - if stat and stat.uid == uid and stat.modestr == mode then - return loadfile(file) - end - end - - local function _write_sane(file, func) - if nixio.getuid() == uid then - local fp = io.open(file, "w") - if fp then - fp:write(util.get_bytecode(func)) - fp:close() - fs.chmod(file, mode) - end - end - end - - package.loaders[2] = function(mod) - local encoded = cachepath .. "/" .. _encode_filename(mod) - local modcons = _load_sane(encoded) - - if modcons then - return modcons - end - - -- No cachefile - modcons = loader(mod) - if type(modcons) == "function" then - _write_sane(encoded, modcons) - end - return modcons - end -end diff --git a/modules/luci-base/luasrc/config.lua b/modules/luci-base/luasrc/config.lua deleted file mode 100644 index d01153f4f5..0000000000 --- a/modules/luci-base/luasrc/config.lua +++ /dev/null @@ -1,18 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -local util = require "luci.util" -module("luci.config", - function(m) - if pcall(require, "luci.model.uci") then - local config = util.threadlocal() - setmetatable(m, { - __index = function(tbl, key) - if not config[key] then - config[key] = luci.model.uci.cursor():get_all("luci", key) - end - return config[key] - end - }) - end - end) diff --git a/modules/luci-base/luasrc/controller/admin/index.lua b/modules/luci-base/luasrc/controller/admin/index.lua deleted file mode 100644 index 8f9b481cce..0000000000 --- a/modules/luci-base/luasrc/controller/admin/index.lua +++ /dev/null @@ -1,199 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -module("luci.controller.admin.index", package.seeall) - -function action_logout() - local dsp = require "luci.dispatcher" - local utl = require "luci.util" - local sid = dsp.context.authsession - - if sid then - utl.ubus("session", "destroy", { ubus_rpc_session = sid }) - - local url = dsp.build_url() - - if luci.http.getenv('HTTPS') == 'on' then - luci.http.header("Set-Cookie", "sysauth_https=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=%s" % url) - end - - luci.http.header("Set-Cookie", "sysauth_http=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=%s" % url) - end - - luci.http.redirect(dsp.build_url()) -end - -function action_translations(lang) - local i18n = require "luci.i18n" - local http = require "luci.http" - local fs = require "nixio".fs - - if lang and #lang > 0 then - lang = i18n.setlanguage(lang) - if lang then - local s = fs.stat("%s/base.%s.lmo" %{ i18n.i18ndir, lang }) - if s then - http.header("Cache-Control", "public, max-age=31536000") - http.header("ETag", "%x-%x-%x" %{ s["ino"], s["size"], s["mtime"] }) - end - end - end - - http.prepare_content("application/javascript; charset=utf-8") - http.write("window.TR=") - http.write_json(i18n.dump()) -end - -local function ubus_reply(id, data, code, errmsg) - local reply = { jsonrpc = "2.0", id = id } - if errmsg then - reply.error = { - code = code, - message = errmsg - } - elseif type(code) == "table" then - reply.result = code - else - reply.result = { code, data } - end - - return reply -end - -local ubus_types = { - nil, - "array", - "object", - "string", - nil, -- INT64 - "number", - nil, -- INT16, - "boolean", - "double" -} - -local function ubus_access(sid, obj, fun) - local res, code = luci.util.ubus("session", "access", { - ubus_rpc_session = sid, - scope = "ubus", - object = obj, - ["function"] = fun - }) - - return (type(res) == "table" and res.access == true) -end - -local function ubus_request(req) - if type(req) ~= "table" or type(req.method) ~= "string" or req.jsonrpc ~= "2.0" or req.id == nil then - return ubus_reply(nil, nil, -32600, "Invalid request") - - elseif req.method == "call" then - if type(req.params) ~= "table" or #req.params < 3 then - return ubus_reply(nil, nil, -32600, "Invalid parameters") - end - - local sid, obj, fun, arg = - req.params[1], req.params[2], req.params[3], req.params[4] or {} - if type(arg) ~= "table" or arg.ubus_rpc_session ~= nil then - return ubus_reply(req.id, nil, -32602, "Invalid parameters") - end - - if sid == "00000000000000000000000000000000" and luci.dispatcher.context.authsession then - sid = luci.dispatcher.context.authsession - end - - if not ubus_access(sid, obj, fun) then - return ubus_reply(req.id, nil, -32002, "Access denied") - end - - arg.ubus_rpc_session = sid - - local res, code = luci.util.ubus(obj, fun, arg) - return ubus_reply(req.id, res, code or 0) - - elseif req.method == "list" then - if req.params == nil or (type(req.params) == "table" and #req.params == 0) then - local objs = luci.util.ubus() - return ubus_reply(req.id, nil, objs) - - elseif type(req.params) == "table" then - local n, rv = nil, {} - for n = 1, #req.params do - if type(req.params[n]) ~= "string" then - return ubus_reply(req.id, nil, -32602, "Invalid parameters") - end - - local sig = luci.util.ubus(req.params[n]) - if sig and type(sig) == "table" then - rv[req.params[n]] = {} - - local m, p - for m, p in pairs(sig) do - if type(p) == "table" then - rv[req.params[n]][m] = {} - - local pn, pt - for pn, pt in pairs(p) do - rv[req.params[n]][m][pn] = ubus_types[pt] or "unknown" - end - end - end - end - end - return ubus_reply(req.id, nil, rv) - - else - return ubus_reply(req.id, nil, -32602, "Invalid parameters") - end - end - - return ubus_reply(req.id, nil, -32601, "Method not found") -end - -function action_ubus() - local parser = require "luci.jsonc".new() - - luci.http.context.request:setfilehandler(function(_, s) - if not s then - return nil - end - - local ok, err = parser:parse(s) - return (not err or nil) - end) - - luci.http.context.request:content() - - local json = parser:get() - if json == nil or type(json) ~= "table" then - luci.http.prepare_content("application/json") - luci.http.write_json(ubus_reply(nil, nil, -32700, "Parse error")) - return - end - - local response - if #json == 0 then - response = ubus_request(json) - else - response = {} - - local _, request - for _, request in ipairs(json) do - response[_] = ubus_request(request) - end - end - - luci.http.prepare_content("application/json") - luci.http.write_json(response) -end - -function action_menu() - local dsp = require "luci.dispatcher" - local http = require "luci.http" - - local _, _, acls = dsp.is_authenticated({ methods = { "cookie:sysauth_https", "cookie:sysauth_http" } }) - local menu = dsp.menu_json(acls or {}) or {} - - http.prepare_content("application/json") - http.write_json(menu) -end diff --git a/modules/luci-base/luasrc/controller/admin/uci.lua b/modules/luci-base/luasrc/controller/admin/uci.lua deleted file mode 100644 index 7aad10d58a..0000000000 --- a/modules/luci-base/luasrc/controller/admin/uci.lua +++ /dev/null @@ -1,70 +0,0 @@ --- Copyright 2008 Steven Barth --- Copyright 2010-2019 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -module("luci.controller.admin.uci", package.seeall) - -local function ubus_state_to_http(errstr) - local map = { - ["Invalid command"] = 400, - ["Invalid argument"] = 400, - ["Method not found"] = 404, - ["Entry not found"] = 404, - ["No data"] = 204, - ["Permission denied"] = 403, - ["Timeout"] = 504, - ["Not supported"] = 500, - ["Unknown error"] = 500, - ["Connection failed"] = 503 - } - - local code = map[errstr] or 200 - local msg = errstr or "OK" - - luci.http.status(code, msg) - - if code ~= 204 then - luci.http.prepare_content("text/plain") - luci.http.write(msg) - end -end - -function action_apply_rollback() - local uci = require "luci.model.uci" - local token, errstr = uci:apply(true) - if token then - luci.http.prepare_content("application/json") - luci.http.write_json({ token = token }) - else - ubus_state_to_http(errstr) - end -end - -function action_apply_unchecked() - local uci = require "luci.model.uci" - local _, errstr = uci:apply(false) - ubus_state_to_http(errstr) -end - -function action_confirm() - local uci = require "luci.model.uci" - local token = luci.http.formvalue("token") - local _, errstr = uci:confirm(token) - ubus_state_to_http(errstr) -end - -function action_revert() - local uci = require "luci.model.uci" - local changes = uci:changes() - - -- Collect files to be reverted - local _, errstr, r, tbl - for r, tbl in pairs(changes) do - _, errstr = uci:revert(r) - if errstr then - break - end - end - - ubus_state_to_http(errstr or "OK") -end diff --git a/modules/luci-base/luasrc/dispatcher.lua b/modules/luci-base/luasrc/dispatcher.lua deleted file mode 100644 index a3726fb1c1..0000000000 --- a/modules/luci-base/luasrc/dispatcher.lua +++ /dev/null @@ -1,1564 +0,0 @@ --- Copyright 2008 Steven Barth --- Copyright 2008-2015 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local sys = require "luci.sys" -local util = require "luci.util" -local xml = require "luci.xml" -local http = require "luci.http" -local nixio = require "nixio", require "nixio.util" - -module("luci.dispatcher", package.seeall) -context = util.threadlocal() -uci = require "luci.model.uci" -i18n = require "luci.i18n" -_M.fs = fs - --- Index table -local index = nil - -local function check_fs_depends(spec) - local fs = require "nixio.fs" - - for path, kind in pairs(spec) do - if kind == "directory" then - local empty = true - for entry in (fs.dir(path) or function() end) do - empty = false - break - end - if empty then - return false - end - elseif kind == "executable" then - if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then - return false - end - elseif kind == "file" then - if fs.stat(path, "type") ~= "reg" then - return false - end - elseif kind == "absent" then - if fs.stat(path, "type") then - return false - end - end - end - - return true -end - -local function check_uci_depends_options(conf, s, opts) - local uci = require "luci.model.uci" - - if type(opts) == "string" then - return (s[".type"] == opts) - elseif opts == true then - for option, value in pairs(s) do - if option:byte(1) ~= 46 then - return true - end - end - elseif type(opts) == "table" then - for option, value in pairs(opts) do - local sval = s[option] - if type(sval) == "table" then - local found = false - for _, v in ipairs(sval) do - if v == value then - found = true - break - end - end - if not found then - return false - end - elseif value == true then - if sval == nil then - return false - end - else - if sval ~= value then - return false - end - end - end - end - - return true -end - -local function check_uci_depends_section(conf, sect) - local uci = require "luci.model.uci" - - for section, options in pairs(sect) do - local stype = section:match("^@([A-Za-z0-9_%-]+)$") - if stype then - local found = false - uci:foreach(conf, stype, function(s) - if check_uci_depends_options(conf, s, options) then - found = true - return false - end - end) - if not found then - return false - end - else - local s = uci:get_all(conf, section) - if not s or not check_uci_depends_options(conf, s, options) then - return false - end - end - end - - return true -end - -local function check_uci_depends(conf) - local uci = require "luci.model.uci" - - for config, values in pairs(conf) do - if values == true then - local found = false - uci:foreach(config, nil, function(s) - found = true - return false - end) - if not found then - return false - end - elseif type(values) == "table" then - if not check_uci_depends_section(config, values) then - return false - end - end - end - - return true -end - -local function check_acl_depends(require_groups, groups) - if type(require_groups) == "table" and #require_groups > 0 then - local writable = false - - for _, group in ipairs(require_groups) do - local read = false - local write = false - if type(groups) == "table" and type(groups[group]) == "table" then - for _, perm in ipairs(groups[group]) do - if perm == "read" then - read = true - elseif perm == "write" then - write = true - end - end - end - if not read and not write then - return nil - elseif write then - writable = true - end - end - - return writable - end - - return true -end - -local function check_depends(spec) - if type(spec.depends) ~= "table" then - return true - end - - if type(spec.depends.fs) == "table" then - local satisfied = false - local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs } - for _, alternative in ipairs(alternatives) do - if check_fs_depends(alternative) then - satisfied = true - break - end - end - if not satisfied then - return false - end - end - - if type(spec.depends.uci) == "table" then - local satisfied = false - local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci } - for _, alternative in ipairs(alternatives) do - if check_uci_depends(alternative) then - satisfied = true - break - end - end - if not satisfied then - return false - end - end - - return true -end - -local function target_to_json(target, module) - local action - - if target.type == "call" then - action = { - ["type"] = "call", - ["module"] = module, - ["function"] = target.name, - ["parameters"] = target.argv - } - elseif target.type == "view" then - action = { - ["type"] = "view", - ["path"] = target.view - } - elseif target.type == "template" then - action = { - ["type"] = "template", - ["path"] = target.view - } - elseif target.type == "cbi" then - action = { - ["type"] = "cbi", - ["path"] = target.model, - ["config"] = target.config - } - elseif target.type == "form" then - action = { - ["type"] = "form", - ["path"] = target.model - } - elseif target.type == "firstchild" then - action = { - ["type"] = "firstchild" - } - elseif target.type == "firstnode" then - action = { - ["type"] = "firstchild", - ["recurse"] = true - } - elseif target.type == "arcombine" then - if type(target.targets) == "table" then - action = { - ["type"] = "arcombine", - ["targets"] = { - target_to_json(target.targets[1], module), - target_to_json(target.targets[2], module) - } - } - end - elseif target.type == "alias" then - action = { - ["type"] = "alias", - ["path"] = table.concat(target.req, "/") - } - elseif target.type == "rewrite" then - action = { - ["type"] = "rewrite", - ["path"] = table.concat(target.req, "/"), - ["remove"] = target.n - } - end - - if target.post and action then - action.post = target.post - end - - return action -end - -local function tree_to_json(node, json) - local fs = require "nixio.fs" - local util = require "luci.util" - - if type(node.nodes) == "table" then - for subname, subnode in pairs(node.nodes) do - local spec = { - title = xml.striptags(subnode.title), - order = subnode.order - } - - if subnode.leaf then - spec.wildcard = true - end - - if subnode.cors then - spec.cors = true - end - - if subnode.setuser then - spec.setuser = subnode.setuser - end - - if subnode.setgroup then - spec.setgroup = subnode.setgroup - end - - if type(subnode.target) == "table" then - spec.action = target_to_json(subnode.target, subnode.module) - end - - if type(subnode.file_depends) == "table" then - for _, v in ipairs(subnode.file_depends) do - spec.depends = spec.depends or {} - spec.depends.fs = spec.depends.fs or {} - - local ft = fs.stat(v, "type") - if ft == "dir" then - spec.depends.fs[v] = "directory" - elseif v:match("/s?bin/") then - spec.depends.fs[v] = "executable" - else - spec.depends.fs[v] = "file" - end - end - end - - if type(subnode.uci_depends) == "table" then - for k, v in pairs(subnode.uci_depends) do - spec.depends = spec.depends or {} - spec.depends.uci = spec.depends.uci or {} - spec.depends.uci[k] = v - end - end - - if type(subnode.acl_depends) == "table" then - for _, acl in ipairs(subnode.acl_depends) do - spec.depends = spec.depends or {} - spec.depends.acl = spec.depends.acl or {} - spec.depends.acl[#spec.depends.acl + 1] = acl - end - end - - if (subnode.sysauth_authenticator ~= nil) or - (subnode.sysauth ~= nil and subnode.sysauth ~= false) - then - if subnode.sysauth_authenticator == "htmlauth" then - spec.auth = { - login = true, - methods = { "cookie:sysauth_https", "cookie:sysauth_http" } - } - elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then - spec.auth = { - login = false, - methods = { "query:auth", "cookie:sysauth_https", "cookie:sysauth_http" } - } - elseif subnode.module == "luci.controller.admin.uci" then - spec.auth = { - login = false, - methods = { "param:sid" } - } - end - elseif subnode.sysauth == false then - spec.auth = {} - end - - if not spec.action then - spec.title = nil - end - - spec.satisfied = check_depends(spec) - json.children = json.children or {} - json.children[subname] = tree_to_json(subnode, spec) - end - end - - return json -end - -function build_url(...) - local path = {...} - local url = { http.getenv("SCRIPT_NAME") or "" } - - local p - for _, p in ipairs(path) do - if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then - url[#url+1] = "/" - url[#url+1] = p - end - end - - if #path == 0 then - url[#url+1] = "/" - end - - return table.concat(url, "") -end - - -function error404(message) - http.status(404, "Not Found") - message = message or "Not Found" - - local function render() - local template = require "luci.template" - template.render("error404", {message=message}) - end - - if not util.copcall(render) then - http.prepare_content("text/plain") - http.write(message) - end - - return false -end - -function error500(message) - util.perror(message) - if not context.template_header_sent then - http.status(500, "Internal Server Error") - http.prepare_content("text/plain") - http.write(message) - else - require("luci.template") - if not util.copcall(luci.template.render, "error500", {message=message}) then - http.prepare_content("text/plain") - http.write(message) - end - end - return false -end - -local function determine_request_language() - local conf = require "luci.config" - assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'") - - local lang = conf.main.lang or "auto" - if lang == "auto" then - local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or "" - for aclang in aclang:gmatch("[%w_-]+") do - local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$") - if country and culture then - local cc = "%s_%s" %{ country, culture:lower() } - if conf.languages[cc] then - lang = cc - break - elseif conf.languages[country] then - lang = country - break - end - elseif conf.languages[aclang] then - lang = aclang - break - end - end - end - - if lang == "auto" then - lang = i18n.default - end - - i18n.setlanguage(lang) -end - -function httpdispatch(request, prefix) - http.context.request = request - - local r = {} - context.request = r - - local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) - - if prefix then - for _, node in ipairs(prefix) do - r[#r+1] = node - end - end - - local node - for node in pathinfo:gmatch("[^/%z]+") do - r[#r+1] = node - end - - determine_request_language() - - local stat, err = util.coxpcall(function() - dispatch(context.request) - end, error500) - - http.close() - - --context._disable_memtrace() -end - -local function require_post_security(target, args) - if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then - return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args) - end - - if type(target) == "table" then - if type(target.post) == "table" then - local param_name, required_val, request_val - - for param_name, required_val in pairs(target.post) do - request_val = http.formvalue(param_name) - - if (type(required_val) == "string" and - request_val ~= required_val) or - (required_val == true and request_val == nil) - then - return false - end - end - - return true - end - - return (target.post == true) - end - - return false -end - -function test_post_security() - if http.getenv("REQUEST_METHOD") ~= "POST" then - http.status(405, "Method Not Allowed") - http.header("Allow", "POST") - return false - end - - if http.formvalue("token") ~= context.authtoken then - http.status(403, "Forbidden") - luci.template.render("csrftoken") - return false - end - - return true -end - -local function session_retrieve(sid, allowed_users) - local sdat = util.ubus("session", "get", { ubus_rpc_session = sid }) - local sacl = util.ubus("session", "access", { ubus_rpc_session = sid }) - - if type(sdat) == "table" and - type(sdat.values) == "table" and - type(sdat.values.token) == "string" and - (not allowed_users or - util.contains(allowed_users, sdat.values.username)) - then - uci:set_session_id(sid) - return sid, sdat.values, type(sacl) == "table" and sacl or {} - end - - return nil, nil, nil -end - -local function session_setup(user, pass) - local login = util.ubus("session", "login", { - username = user, - password = pass, - timeout = tonumber(luci.config.sauth.sessiontime) - }) - - local rp = context.requestpath - and table.concat(context.requestpath, "/") or "" - - if type(login) == "table" and - type(login.ubus_rpc_session) == "string" - then - util.ubus("session", "set", { - ubus_rpc_session = login.ubus_rpc_session, - values = { token = sys.uniqueid(16) } - }) - nixio.syslog("info", tostring("luci: accepted login on /%s for %s from %s\n" - %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })) - - return session_retrieve(login.ubus_rpc_session) - end - nixio.syslog("info", tostring("luci: failed login on /%s for %s from %s\n" - %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })) -end - -local function check_authentication(method) - local auth_type, auth_param = method:match("^(%w+):(.+)$") - local sid, sdat - - if auth_type == "cookie" then - sid = http.getcookie(auth_param) - elseif auth_type == "param" then - sid = http.formvalue(auth_param) - elseif auth_type == "query" then - sid = http.formvalue(auth_param, true) - end - - return session_retrieve(sid) -end - -local function merge_trees(node_a, node_b) - for k, v in pairs(node_b) do - if k == "children" then - node_a.children = node_a.children or {} - - for name, spec in pairs(v) do - node_a.children[name] = merge_trees(node_a.children[name] or {}, spec) - end - else - node_a[k] = v - end - end - - if type(node_a.action) == "table" and - node_a.action.type == "firstchild" and - node_a.children == nil - then - node_a.satisfied = false - end - - return node_a -end - -local function apply_tree_acls(node, acl) - if type(node.children) == "table" then - for _, child in pairs(node.children) do - apply_tree_acls(child, acl) - end - end - - local perm - if type(node.depends) == "table" then - perm = check_acl_depends(node.depends.acl, acl["access-group"]) - else - perm = true - end - - if perm == nil then - node.satisfied = false - elseif perm == false then - node.readonly = true - end -end - -function menu_json(acl) - local tree = context.tree or createtree() - local lua_tree = tree_to_json(tree, { - action = { - ["type"] = "firstchild", - ["recurse"] = true - } - }) - - local json_tree = createtree_json() - local menu_tree = merge_trees(lua_tree, json_tree) - - if acl then - apply_tree_acls(menu_tree, acl) - end - - return menu_tree -end - -local function init_template_engine(ctx) - local tpl = require "luci.template" - local media = luci.config.main.mediaurlbase - - if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then - media = nil - for name, theme in pairs(luci.config.themes) do - if name:sub(1,1) ~= "." and pcall(tpl.Template, - "themes/%s/header" % fs.basename(theme)) then - media = theme - end - end - assert(media, "No valid theme found") - end - - local function _ifattr(cond, key, val, noescape) - if cond then - local env = getfenv(3) - local scope = (type(env.self) == "table") and env.self - if type(val) == "table" then - if not next(val) then - return '' - else - val = util.serialize_json(val) - end - end - - val = tostring(val or - (type(env[key]) ~= "function" and env[key]) or - (scope and type(scope[key]) ~= "function" and scope[key]) or "") - - if noescape ~= true then - val = xml.pcdata(val) - end - - return string.format(' %s="%s"', tostring(key), val) - else - return '' - end - end - - tpl.context.viewns = setmetatable({ - write = http.write; - include = function(name) tpl.Template(name):render(getfenv(2)) end; - translate = i18n.translate; - translatef = i18n.translatef; - export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end; - striptags = xml.striptags; - pcdata = xml.pcdata; - media = media; - theme = fs.basename(media); - resource = luci.config.main.resourcebase; - ifattr = function(...) return _ifattr(...) end; - attr = function(...) return _ifattr(true, ...) end; - url = build_url; - }, {__index=function(tbl, key) - if key == "controller" then - return build_url() - elseif key == "REQUEST_URI" then - return build_url(unpack(ctx.requestpath)) - elseif key == "FULL_REQUEST_URI" then - local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") } - local query = http.getenv("QUERY_STRING") - if query and #query > 0 then - url[#url+1] = "?" - url[#url+1] = query - end - return table.concat(url, "") - elseif key == "token" then - return ctx.authtoken - else - return rawget(tbl, key) or _G[key] - end - end}) - - return tpl -end - -function is_authenticated(auth) - if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then - local sid, sdat, sacl - for _, method in ipairs(auth.methods) do - sid, sdat, sacl = check_authentication(method) - - if sid and sdat and sacl then - return sid, sdat, sacl - end - end - end -end - -local function ctx_append(ctx, name, node) - ctx.path = ctx.path or {} - ctx.path[#ctx.path + 1] = name - - ctx.acls = ctx.acls or {} - - local acls = (type(node.depends) == "table" and type(node.depends.acl) == "table") and node.depends.acl or {} - for _, acl in ipairs(acls) do - ctx.acls[_] = acl - end - - ctx.auth = node.auth or ctx.auth - ctx.cors = node.cors or ctx.cors - ctx.suid = node.setuser or ctx.suid - ctx.sgid = node.setgroup or ctx.sgid - - return ctx -end - -local function node_weight(node) - local weight = node.order or 9999 - - if weight > 9999 then - weight = 9999 - end - - if type(node.auth) == "table" and node.auth.login then - weight = weight + 10000 - end - - return weight -end - -local function resolve_firstchild(node, sacl, login_allowed, ctx) - local candidate = nil - local candidate_ctx = nil - - for name, child in pairs(node.children) do - if child.satisfied then - if not sacl then - local _ - _, _, sacl = is_authenticated(node.auth) - end - - local cacl = (type(child.depends) == "table") and child.depends.acl or nil - local login = login_allowed or (type(child.auth) == "table" and child.auth.login) - if login or check_acl_depends(cacl, sacl and sacl["access-group"]) ~= nil then - if child.title and type(child.action) == "table" then - local child_ctx = ctx_append(util.clone(ctx, true), name, child) - if child.action.type == "firstchild" then - if not candidate or node_weight(candidate) > node_weight(child) then - local have_grandchild = resolve_firstchild(child, sacl, login, child_ctx) - if have_grandchild then - candidate = child - candidate_ctx = child_ctx - end - end - elseif not child.firstchild_ineligible then - if not candidate or node_weight(candidate) > node_weight(child) then - candidate = child - candidate_ctx = child_ctx - end - end - end - end - end - end - - if candidate then - for k, v in pairs(candidate_ctx) do - ctx[k] = v - end - - return true - end - - return false -end - -local function resolve_page(tree, request_path) - local node = tree - local sacl = nil - local login = false - local ctx = {} - - for i, s in ipairs(request_path) do - node = node.children and node.children[s] - - if not node or not node.satisfied then - break - end - - ctx_append(ctx, s, node) - - if not sacl then - local _ - _, _, sacl = is_authenticated(node.auth) - end - - if not login and type(node.auth) == "table" and node.auth.login then - login = true - end - - if node.wildcard then - ctx.request_args = {} - ctx.request_path = util.clone(ctx.path, true) - - for j = i + 1, #request_path do - ctx.request_path[j] = request_path[j] - ctx.request_args[j - i] = request_path[j] - end - - break - end - end - - if node and type(node.action) == "table" and node.action.type == "firstchild" then - resolve_firstchild(node, sacl, login, ctx) - end - - ctx.acls = ctx.acls or {} - ctx.path = ctx.path or {} - ctx.request_args = ctx.request_args or {} - ctx.request_path = ctx.request_path or util.clone(request_path, true) - - node = tree - - for _, s in ipairs(ctx.path or {}) do - node = node.children[s] - assert(node, "Internal node resolve error") - end - - return node, ctx -end - -function dispatch(request) - --context._disable_memtrace = require "luci.debug".trap_memtrace("l") - local ctx = context - - local auth, cors, suid, sgid - local menu = menu_json() - local page, lookup_ctx = resolve_page(menu, request) - local action = (page and type(page.action) == "table") and page.action or {} - - local tpl = init_template_engine(ctx) - - ctx.args = lookup_ctx.request_args - ctx.path = lookup_ctx.path - ctx.dispatched = page - - ctx.requestpath = ctx.requestpath or lookup_ctx.request_path - ctx.requestargs = ctx.requestargs or lookup_ctx.request_args - ctx.requested = ctx.requested or page - - if type(lookup_ctx.auth) == "table" and next(lookup_ctx.auth) then - local sid, sdat, sacl = is_authenticated(lookup_ctx.auth) - - if not (sid and sdat and sacl) and lookup_ctx.auth.login then - local user = http.getenv("HTTP_AUTH_USER") - local pass = http.getenv("HTTP_AUTH_PASS") - - if user == nil and pass == nil then - user = http.formvalue("luci_username") - pass = http.formvalue("luci_password") - end - - if user and pass then - sid, sdat, sacl = session_setup(user, pass) - end - - if not sid then - context.path = {} - - http.status(403, "Forbidden") - http.header("X-LuCI-Login-Required", "yes") - - local scope = { duser = "root", fuser = user } - local ok, res = util.copcall(tpl.render_string, [[<% include("themes/" .. theme .. "/sysauth") %>]], scope) - if ok then - return res - end - return tpl.render("sysauth", scope) - end - - http.header("Set-Cookie", 'sysauth_%s=%s; path=%s; SameSite=Strict; HttpOnly%s' %{ - http.getenv("HTTPS") == "on" and "https" or "http", - sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or "" - }) - - http.redirect(build_url(unpack(ctx.requestpath))) - return - end - - if not sid or not sdat or not sacl then - http.status(403, "Forbidden") - http.header("X-LuCI-Login-Required", "yes") - return - end - - ctx.authsession = sid - ctx.authtoken = sdat.token - ctx.authuser = sdat.username - ctx.authacl = sacl - end - - if #lookup_ctx.acls > 0 then - local perm = check_acl_depends(lookup_ctx.acls, ctx.authacl and ctx.authacl["access-group"]) - if perm == nil then - http.status(403, "Forbidden") - return - end - - if page then - page.readonly = not perm - end - end - - if action.type == "arcombine" then - action = (#lookup_ctx.request_args > 0) and action.targets[2] or action.targets[1] - end - - if lookup_ctx.cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then - luci.http.status(200, "OK") - luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*") - luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - return - end - - if require_post_security(action) then - if not test_post_security() then - return - end - end - - if lookup_ctx.sgid then - sys.process.setgroup(lookup_ctx.sgid) - end - - if lookup_ctx.suid then - sys.process.setuser(lookup_ctx.suid) - end - - if action.type == "view" then - tpl.render("view", { view = action.path }) - - elseif action.type == "call" then - local ok, mod = util.copcall(require, action.module) - if not ok then - error500(mod) - return - end - - local func = mod[action["function"]] - - assert(func ~= nil, - 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?') - - assert(type(func) == "function", - 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' .. - 'of type "' .. type(func) .. '".') - - local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {} - for _, s in ipairs(lookup_ctx.request_args) do - argv[#argv + 1] = s - end - - local ok, err = util.copcall(func, unpack(argv)) - if not ok then - error500(err) - end - - --elseif action.type == "firstchild" then - -- tpl.render("empty_node_placeholder", getfenv(1)) - - elseif action.type == "alias" then - local sub_request = {} - for name in action.path:gmatch("[^/]+") do - sub_request[#sub_request + 1] = name - end - - for _, s in ipairs(lookup_ctx.request_args) do - sub_request[#sub_request + 1] = s - end - - dispatch(sub_request) - - elseif action.type == "rewrite" then - local sub_request = { unpack(request) } - for i = 1, action.remove do - table.remove(sub_request, 1) - end - - local n = 1 - for s in action.path:gmatch("[^/]+") do - table.insert(sub_request, n, s) - n = n + 1 - end - - for _, s in ipairs(lookup_ctx.request_args) do - sub_request[#sub_request + 1] = s - end - - dispatch(sub_request) - - elseif action.type == "template" then - tpl.render(action.path, getfenv(1)) - - elseif action.type == "cbi" then - _cbi({ config = action.config, model = action.path }, unpack(lookup_ctx.request_args)) - - elseif action.type == "form" then - _form({ model = action.path }, unpack(lookup_ctx.request_args)) - - else - if not menu.children then - error404("No root node was registered, this usually happens if no module was installed.\n" .. - "Install luci-mod-admin-full and retry. " .. - "If the module is already installed, try removing the /tmp/luci-indexcache file.") - else - error404("No page is registered at '/" .. table.concat(lookup_ctx.request_path, "/") .. "'.\n" .. - "If this url belongs to an extension, make sure it is properly installed.\n" .. - "If the extension was recently installed, try removing the /tmp/luci-indexcache file.") - end - end -end - -local function hash_filelist(files) - local fprint = {} - local n = 0 - - for i, file in ipairs(files) do - local st = fs.stat(file) - if st then - fprint[n + 1] = '%x' % st.ino - fprint[n + 2] = '%x' % st.mtime - fprint[n + 3] = '%x' % st.size - n = n + 3 - end - end - - return nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".") -end - -local function read_cachefile(file, reader) - local euid = sys.process.info("uid") - local fuid = fs.stat(file, "uid") - local mode = fs.stat(file, "modestr") - - if euid ~= fuid or mode ~= "rw-------" then - return nil - end - - return reader(file) -end - -function createindex() - local controllers = { } - local base = "%s/controller/" % util.libpath() - local _, path - - for path in (fs.glob("%s*.lua" % base) or function() end) do - controllers[#controllers+1] = path - end - - for path in (fs.glob("%s*/*.lua" % base) or function() end) do - controllers[#controllers+1] = path - end - - local cachefile - - if indexcache then - cachefile = "%s.%s.lua" %{ indexcache, hash_filelist(controllers) } - - local res = read_cachefile(cachefile, function(path) return loadfile(path)() end) - if res then - index = res - return res - end - - for file in (fs.glob("%s.*.lua" % indexcache) or function() end) do - fs.unlink(file) - end - end - - index = {} - - for _, path in ipairs(controllers) do - local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".") - local mod = require(modname) - assert(mod ~= true, - "Invalid controller file found\n" .. - "The file '" .. path .. "' contains an invalid module line.\n" .. - "Please verify whether the module name is set to '" .. modname .. - "' - It must correspond to the file path!") - - local idx = mod.index - if type(idx) == "function" then - index[modname] = idx - end - end - - if cachefile then - local f = nixio.open(cachefile, "w", 600) - f:writeall(util.get_bytecode(index)) - f:close() - end -end - -function createtree_json() - local json = require "luci.jsonc" - local tree = {} - - local schema = { - action = "table", - auth = "table", - cors = "boolean", - depends = "table", - order = "number", - setgroup = "string", - setuser = "string", - title = "string", - wildcard = "boolean", - firstchild_ineligible = "boolean" - } - - local files = {} - local cachefile - - for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do - files[#files+1] = file - end - - if indexcache then - cachefile = "%s.%s.json" %{ indexcache, hash_filelist(files) } - - local res = read_cachefile(cachefile, function(path) return json.parse(fs.readfile(path) or "") end) - if res then - return res - end - - for file in (fs.glob("%s.*.json" % indexcache) or function() end) do - fs.unlink(file) - end - end - - for _, file in ipairs(files) do - local data = json.parse(fs.readfile(file) or "") - if type(data) == "table" then - for path, spec in pairs(data) do - if type(spec) == "table" then - local node = tree - - for s in path:gmatch("[^/]+") do - if s == "*" then - node.wildcard = true - break - end - - node.children = node.children or {} - node.children[s] = node.children[s] or {} - node = node.children[s] - end - - if node ~= tree then - for k, t in pairs(schema) do - if type(spec[k]) == t then - node[k] = spec[k] - end - end - - node.satisfied = check_depends(spec) - end - end - end - end - end - - if cachefile then - local f = nixio.open(cachefile, "w", 600) - f:writeall(json.stringify(tree)) - f:close() - end - - return tree -end - --- Build the index before if it does not exist yet. -function createtree() - if not index then - createindex() - end - - local ctx = context - local tree = {nodes={}, inreq=true} - - ctx.treecache = setmetatable({}, {__mode="v"}) - ctx.tree = tree - - local scope = setmetatable({}, {__index = luci.dispatcher}) - - for k, v in pairs(index) do - scope._NAME = k - setfenv(v, scope) - v() - end - - return tree -end - -function assign(path, clone, title, order) - local obj = node(unpack(path)) - obj.nodes = nil - obj.module = nil - - obj.title = title - obj.order = order - - setmetatable(obj, {__index = _create_node(clone)}) - - return obj -end - -function entry(path, target, title, order) - local c = node(unpack(path)) - - c.target = target - c.title = title - c.order = order - c.module = getfenv(2)._NAME - - return c -end - --- enabling the node. -function get(...) - return _create_node({...}) -end - -function node(...) - local c = _create_node({...}) - - c.module = getfenv(2)._NAME - c.auto = nil - - return c -end - -function lookup(...) - local i, path = nil, {} - for i = 1, select('#', ...) do - local name, arg = nil, tostring(select(i, ...)) - for name in arg:gmatch("[^/]+") do - path[#path+1] = name - end - end - - for i = #path, 1, -1 do - local node = context.treecache[table.concat(path, ".", 1, i)] - if node and (i == #path or node.leaf) then - return node, build_url(unpack(path)) - end - end -end - -function _create_node(path) - if #path == 0 then - return context.tree - end - - local name = table.concat(path, ".") - local c = context.treecache[name] - - if not c then - local last = table.remove(path) - local parent = _create_node(path) - - c = {nodes={}, auto=true, inreq=true} - - parent.nodes[last] = c - context.treecache[name] = c - end - - return c -end - --- Subdispatchers -- - -function firstchild() - return { type = "firstchild" } -end - -function firstnode() - return { type = "firstnode" } -end - -function alias(...) - return { type = "alias", req = { ... } } -end - -function rewrite(n, ...) - return { type = "rewrite", n = n, req = { ... } } -end - -function call(name, ...) - return { type = "call", argv = {...}, name = name } -end - -function post_on(params, name, ...) - return { - type = "call", - post = params, - argv = { ... }, - name = name - } -end - -function post(...) - return post_on(true, ...) -end - - -function template(name) - return { type = "template", view = name } -end - -function view(name) - return { type = "view", view = name } -end - - -function _cbi(self, ...) - local cbi = require "luci.cbi" - local tpl = require "luci.template" - local http = require "luci.http" - local util = require "luci.util" - - local config = self.config or {} - local maps = cbi.load(self.model, ...) - - local state = nil - - local function has_uci_access(config, level) - local rv = util.ubus("session", "access", { - ubus_rpc_session = context.authsession, - scope = "uci", object = config, - ["function"] = level - }) - - return (type(rv) == "table" and rv.access == true) or false - end - - local i, res - for i, res in ipairs(maps) do - if util.instanceof(res, cbi.SimpleForm) then - io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n" - % self.model) - - io.stderr:write("please change %s to use the form() action instead.\n" - % table.concat(context.request, "/")) - end - - res.flow = config - local cstate = res:parse() - if cstate and (not state or cstate < state) then - state = cstate - end - end - - local function _resolve_path(path) - return type(path) == "table" and build_url(unpack(path)) or path - end - - if config.on_valid_to and state and state > 0 and state < 2 then - http.redirect(_resolve_path(config.on_valid_to)) - return - end - - if config.on_changed_to and state and state > 1 then - http.redirect(_resolve_path(config.on_changed_to)) - return - end - - if config.on_success_to and state and state > 0 then - http.redirect(_resolve_path(config.on_success_to)) - return - end - - if config.state_handler then - if not config.state_handler(state, maps) then - return - end - end - - http.header("X-CBI-State", state or 0) - - if not config.noheader then - tpl.render("cbi/header", {state = state}) - end - - local redirect - local messages - local applymap = false - local pageaction = true - local parsechain = { } - local writable = false - - for i, res in ipairs(maps) do - if res.apply_needed and res.parsechain then - local c - for _, c in ipairs(res.parsechain) do - parsechain[#parsechain+1] = c - end - applymap = true - end - - if res.redirect then - redirect = redirect or res.redirect - end - - if res.pageaction == false then - pageaction = false - end - - if res.message then - messages = messages or { } - messages[#messages+1] = res.message - end - end - - for i, res in ipairs(maps) do - local is_readable_map = has_uci_access(res.config, "read") - local is_writable_map = has_uci_access(res.config, "write") - - writable = writable or is_writable_map - - res:render({ - firstmap = (i == 1), - redirect = redirect, - messages = messages, - pageaction = pageaction, - parsechain = parsechain, - readable = is_readable_map, - writable = is_writable_map - }) - end - - if not config.nofooter then - tpl.render("cbi/footer", { - flow = config, - pageaction = pageaction, - redirect = redirect, - state = state, - autoapply = config.autoapply, - trigger_apply = applymap, - writable = writable - }) - end -end - -function cbi(model, config) - return { - type = "cbi", - post = { ["cbi.submit"] = true }, - config = config, - model = model - } -end - - -function arcombine(trg1, trg2) - return { - type = "arcombine", - env = getfenv(), - targets = {trg1, trg2} - } -end - - -function _form(self, ...) - local cbi = require "luci.cbi" - local tpl = require "luci.template" - local http = require "luci.http" - - local maps = luci.cbi.load(self.model, ...) - local state = nil - - local i, res - for i, res in ipairs(maps) do - local cstate = res:parse() - if cstate and (not state or cstate < state) then - state = cstate - end - end - - http.header("X-CBI-State", state or 0) - tpl.render("header") - for i, res in ipairs(maps) do - res:render() - end - tpl.render("footer") -end - -function form(model) - return { - type = "form", - post = { ["cbi.submit"] = true }, - model = model - } -end - -translate = i18n.translate - --- This function does not actually translate the given argument but --- is used by build/i18n-scan.pl to find translatable entries. -function _(text) - return text -end diff --git a/modules/luci-base/luasrc/dispatcher.luadoc b/modules/luci-base/luasrc/dispatcher.luadoc deleted file mode 100644 index a77f8d8b07..0000000000 --- a/modules/luci-base/luasrc/dispatcher.luadoc +++ /dev/null @@ -1,220 +0,0 @@ ----[[ -LuCI web dispatcher. -]] -module "luci.dispatcher" - ----[[ -Build the URL relative to the server webroot from given virtual path. - -@class function -@name build_url -@param ... Virtual path -@return Relative URL -]] - ----[[ -Check whether a dispatch node shall be visible - -@class function -@name node_visible -@param node Dispatch node -@return Boolean indicating whether the node should be visible -]] - ----[[ -Return a sorted table of visible children within a given node - -@class function -@name node_childs -@param node Dispatch node -@return Ordered table of child node names -]] - ----[[ -Send a 404 error code and render the "error404" template if available. - -@class function -@name error404 -@param message Custom error message (optional) -@return false -]] - ----[[ -Send a 500 error code and render the "error500" template if available. - -@class function -@name error500 -@param message Custom error message (optional)# -@return false -]] - ----[[ -Dispatch an HTTP request. - -@class function -@name httpdispatch -@param request LuCI HTTP Request object -]] - ----[[ -Dispatches a LuCI virtual path. - -@class function -@name dispatch -@param request Virtual path -]] - ----[[ -Generate the dispatching index using the native file-cache based strategy. - - -@class function -@name createindex -]] - ----[[ -Create the dispatching tree from the index. - -Build the index before if it does not exist yet. - -@class function -@name createtree -]] - ----[[ -Clone a node of the dispatching tree to another position. - -@class function -@name assign -@param path Virtual path destination -@param clone Virtual path source -@param title Destination node title (optional) -@param order Destination node order value (optional) -@return Dispatching tree node -]] - ----[[ -Create a new dispatching node and define common parameters. - -@class function -@name entry -@param path Virtual path -@param target Target function to call when dispatched. -@param title Destination node title -@param order Destination node order value (optional) -@return Dispatching tree node -]] - ----[[ -Fetch or create a dispatching node without setting the target module or -enabling the node. - -@class function -@name get -@param ... Virtual path -@return Dispatching tree node -]] - ----[[ -Fetch or create a new dispatching node. - -@class function -@name node -@param ... Virtual path -@return Dispatching tree node -]] - ----[[ -Lookup node in dispatching tree. - -@class function -@name lookup -@param ... Virtual path -@return Node object, canonical url or nil if the path was not found. -]] - ----[[ -Alias the first (lowest order) page automatically - - -@class function -@name firstchild -]] - ----[[ -Create a redirect to another dispatching node. - -@class function -@name alias -@param ... Virtual path destination -]] - ----[[ -Rewrite the first x path values of the request. - -@class function -@name rewrite -@param n Number of path values to replace -@param ... Virtual path to replace removed path values with -]] - ----[[ -Create a function-call dispatching target. - -@class function -@name call -@param name Target function of local controller -@param ... Additional parameters passed to the function -]] - ----[[ -Create a template render dispatching target. - -@class function -@name template -@param name Template to be rendered -]] - ----[[ -Create a CBI model dispatching target. - -@class function -@name cbi -@param model CBI model to be rendered -]] - ----[[ -Create a combined dispatching target for non argv and argv requests. - -@class function -@name arcombine -@param trg1 Overview Target -@param trg2 Detail Target -]] - ----[[ -Create a CBI form model dispatching target. - -@class function -@name form -@param model CBI form model tpo be rendered -]] - ----[[ -Access the luci.i18n translate() api. - -@class function -@name translate -@param text Text to translate -]] - ----[[ -No-op function used to mark translation entries for menu labels. - -This function does not actually translate the given argument but -is used by build/i18n-scan.pl to find translatable entries. - -@class function -@name _ -]] - diff --git a/modules/luci-base/luasrc/i18n.lua b/modules/luci-base/luasrc/i18n.lua deleted file mode 100644 index 323912b650..0000000000 --- a/modules/luci-base/luasrc/i18n.lua +++ /dev/null @@ -1,55 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -local tparser = require "luci.template.parser" -local util = require "luci.util" -local tostring = tostring - -module "luci.i18n" - -i18ndir = util.libpath() .. "/i18n/" -context = util.threadlocal() -default = "en" - - -function setlanguage(lang) - local code, subcode = lang:match("^([A-Za-z][A-Za-z])[%-_]([A-Za-z][A-Za-z])$") - if not (code and subcode) then - subcode = lang:match("^([A-Za-z][A-Za-z])$") - if not subcode then - return nil - end - end - - context.parent = code and code:lower() - context.lang = context.parent and context.parent.."-"..subcode:lower() or subcode:lower() - - if tparser.load_catalog(context.lang, i18ndir) and - tparser.change_catalog(context.lang) - then - return context.lang - - elseif context.parent then - if tparser.load_catalog(context.parent, i18ndir) and - tparser.change_catalog(context.parent) - then - return context.parent - end - end - - return nil -end - -function translate(key) - return tparser.translate(key) or key -end - -function translatef(key, ...) - return tostring(translate(key)):format(...) -end - -function dump() - local rv = {} - tparser.get_translations(function(k, v) rv[k] = v end) - return rv -end diff --git a/modules/luci-base/luasrc/i18n.luadoc b/modules/luci-base/luasrc/i18n.luadoc deleted file mode 100644 index b76c298565..0000000000 --- a/modules/luci-base/luasrc/i18n.luadoc +++ /dev/null @@ -1,42 +0,0 @@ ----[[ -LuCI translation library. -]] -module "luci.i18n" - ----[[ -Set the context default translation language. - -@class function -@name setlanguage -@param lang An IETF/BCP 47 language tag or ISO3166 country code, e.g. "en-US" or "de" -@return The effective loaded language, e.g. "en" for "en-US" - or nil on failure -]] - ----[[ -Return the translated value for a specific translation key. - -@class function -@name translate -@param key Default translation text -@return Translated string -]] - ----[[ -Return the translated value for a specific translation key and use it as sprintf pattern. - -@class function -@name translatef -@param key Default translation text -@param ... Format parameters -@return Translated and formatted string -]] - ----[[ -Return all currently loaded translation strings as a key-value table. The key is the -hexadecimal representation of the translation key while the value is the translated -text content. - -@class function -@name dump -@return Key-value translation string table. -]] diff --git a/modules/luci-base/luasrc/model/uci.lua b/modules/luci-base/luasrc/model/uci.lua deleted file mode 100644 index 816f6f2053..0000000000 --- a/modules/luci-base/luasrc/model/uci.lua +++ /dev/null @@ -1,508 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -local os = require "os" -local util = require "luci.util" -local table = require "table" - - -local setmetatable, rawget, rawset = setmetatable, rawget, rawset -local require, getmetatable, assert = require, getmetatable, assert -local error, pairs, ipairs, select = error, pairs, ipairs, select -local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack - --- The typical workflow for UCI is: Get a cursor instance from the --- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), --- save the changes to the staging area via Cursor.save and finally --- Cursor.commit the data to the actual config files. --- LuCI then needs to Cursor.apply the changes so daemons etc. are --- reloaded. -module "luci.model.uci" - -local ERRSTR = { - "Invalid command", - "Invalid argument", - "Method not found", - "Entry not found", - "No data", - "Permission denied", - "Timeout", - "Not supported", - "Unknown error", - "Connection failed" -} - -local session_id = nil - -local function call(cmd, args) - if type(args) == "table" and session_id then - args.ubus_rpc_session = session_id - end - return util.ubus("uci", cmd, args) -end - - -function cursor() - return _M -end - -function cursor_state() - return _M -end - -function substate(self) - return self -end - - -function get_confdir(self) - return "/etc/config" -end - -function get_savedir(self) - return "/tmp/.uci" -end - -function get_session_id(self) - return session_id -end - -function set_confdir(self, directory) - return false -end - -function set_savedir(self, directory) - return false -end - -function set_session_id(self, id) - session_id = id - return true -end - - -function load(self, config) - return true -end - -function save(self, config) - return true -end - -function unload(self, config) - return true -end - - -function changes(self, config) - local rv, err = call("changes", { config = config }) - - if type(rv) == "table" and type(rv.changes) == "table" then - return rv.changes - elseif err then - return nil, ERRSTR[err] - else - return { } - end -end - - -function revert(self, config) - local _, err = call("revert", { config = config }) - return (err == nil), ERRSTR[err] -end - -function commit(self, config) - local _, err = call("commit", { config = config }) - return (err == nil), ERRSTR[err] -end - -function apply(self, rollback) - local _, err - - if rollback then - local sys = require "luci.sys" - local conf = require "luci.config" - local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 90) or 0 - - _, err = call("apply", { - timeout = (timeout > 90) and timeout or 90, - rollback = true - }) - - if not err then - local now = os.time() - local token = sys.uniqueid(16) - - util.ubus("session", "set", { - ubus_rpc_session = "00000000000000000000000000000000", - values = { - rollback = { - token = token, - session = session_id, - timeout = now + timeout - } - } - }) - - return token - end - else - _, err = call("changes", {}) - - if not err then - if type(_) == "table" and type(_.changes) == "table" then - local k, v - for k, v in pairs(_.changes) do - _, err = call("commit", { config = k }) - if err then - break - end - end - end - end - - if not err then - _, err = call("apply", { rollback = false }) - end - end - - return (err == nil), ERRSTR[err] -end - -function confirm(self, token) - local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending() - - if is_pending then - if token ~= rollback_token then - return false, "Permission denied" - end - - local _, err = util.ubus("uci", "confirm", { - ubus_rpc_session = rollback_sid - }) - - if not err then - util.ubus("session", "set", { - ubus_rpc_session = "00000000000000000000000000000000", - values = { rollback = {} } - }) - end - - return (err == nil), ERRSTR[err] - end - - return false, "No data" -end - -function rollback(self) - local is_pending, time_remaining, rollback_sid = self:rollback_pending() - - if is_pending then - local _, err = util.ubus("uci", "rollback", { - ubus_rpc_session = rollback_sid - }) - - if not err then - util.ubus("session", "set", { - ubus_rpc_session = "00000000000000000000000000000000", - values = { rollback = {} } - }) - end - - return (err == nil), ERRSTR[err] - end - - return false, "No data" -end - -function rollback_pending(self) - local rv, err = util.ubus("session", "get", { - ubus_rpc_session = "00000000000000000000000000000000", - keys = { "rollback" } - }) - - local now = os.time() - - if type(rv) == "table" and - type(rv.values) == "table" and - type(rv.values.rollback) == "table" and - type(rv.values.rollback.token) == "string" and - type(rv.values.rollback.session) == "string" and - type(rv.values.rollback.timeout) == "number" and - rv.values.rollback.timeout > now - then - return true, - rv.values.rollback.timeout - now, - rv.values.rollback.session, - rv.values.rollback.token - end - - return false, ERRSTR[err] -end - - -function foreach(self, config, stype, callback) - if type(callback) == "function" then - local rv, err = call("get", { - config = config, - type = stype - }) - - if type(rv) == "table" and type(rv.values) == "table" then - local sections = { } - local res = false - local index = 1 - - local _, section - for _, section in pairs(rv.values) do - section[".index"] = section[".index"] or index - sections[index] = section - index = index + 1 - end - - table.sort(sections, function(a, b) - return a[".index"] < b[".index"] - end) - - for _, section in ipairs(sections) do - local continue = callback(section) - res = true - if continue == false then - break - end - end - return res - else - return false, ERRSTR[err] or "No data" - end - else - return false, "Invalid argument" - end -end - -local function _get(self, operation, config, section, option) - if section == nil then - return nil - elseif type(option) == "string" and option:byte(1) ~= 46 then - local rv, err = call(operation, { - config = config, - section = section, - option = option - }) - - if type(rv) == "table" then - return rv.value or nil - elseif err then - return false, ERRSTR[err] - else - return nil - end - elseif option == nil then - local values = self:get_all(config, section) - if values then - return values[".type"], values[".name"] - else - return nil - end - else - return false, "Invalid argument" - end -end - -function get(self, ...) - return _get(self, "get", ...) -end - -function get_state(self, ...) - return _get(self, "state", ...) -end - -function get_all(self, config, section) - local rv, err = call("get", { - config = config, - section = section - }) - - if type(rv) == "table" and type(rv.values) == "table" then - return rv.values - elseif err then - return false, ERRSTR[err] - else - return nil - end -end - -function get_bool(self, ...) - local val = self:get(...) - return (val == "1" or val == "true" or val == "yes" or val == "on") -end - -function get_first(self, config, stype, option, default) - local rv = default - - self:foreach(config, stype, function(s) - local val = not option and s[".name"] or s[option] - - if type(default) == "number" then - val = tonumber(val) - elseif type(default) == "boolean" then - val = (val == "1" or val == "true" or - val == "yes" or val == "on") - end - - if val ~= nil then - rv = val - return false - end - end) - - return rv -end - -function get_list(self, config, section, option) - if config and section and option then - local val = self:get(config, section, option) - return (type(val) == "table" and val or { val }) - end - return { } -end - - -function section(self, config, stype, name, values) - local rv, err = call("add", { - config = config, - type = stype, - name = name, - values = values - }) - - if type(rv) == "table" then - return rv.section - elseif err then - return false, ERRSTR[err] - else - return nil - end -end - - -function add(self, config, stype) - return self:section(config, stype) -end - -function set(self, config, section, option, ...) - if select('#', ...) == 0 then - local sname, err = self:section(config, option, section) - return (not not sname), err - else - local _, err = call("set", { - config = config, - section = section, - values = { [option] = select(1, ...) } - }) - return (err == nil), ERRSTR[err] - end -end - -function set_list(self, config, section, option, value) - if section == nil or option == nil then - return false - elseif value == nil or (type(value) == "table" and #value == 0) then - return self:delete(config, section, option) - elseif type(value) == "table" then - return self:set(config, section, option, value) - else - return self:set(config, section, option, { value }) - end -end - -function tset(self, config, section, values) - local _, err = call("set", { - config = config, - section = section, - values = values - }) - return (err == nil), ERRSTR[err] -end - -function reorder(self, config, section, index) - local sections - - if type(section) == "string" and type(index) == "number" then - local pos = 0 - - sections = { } - - self:foreach(config, nil, function(s) - if pos == index then - pos = pos + 1 - end - - if s[".name"] ~= section then - pos = pos + 1 - sections[pos] = s[".name"] - else - sections[index + 1] = section - end - end) - elseif type(section) == "table" then - sections = section - else - return false, "Invalid argument" - end - - local _, err = call("order", { - config = config, - sections = sections - }) - - return (err == nil), ERRSTR[err] -end - - -function delete(self, config, section, option) - local _, err = call("delete", { - config = config, - section = section, - option = option - }) - return (err == nil), ERRSTR[err] -end - -function delete_all(self, config, stype, comparator) - local _, err - if type(comparator) == "table" then - _, err = call("delete", { - config = config, - type = stype, - match = comparator - }) - elseif type(comparator) == "function" then - local rv = call("get", { - config = config, - type = stype - }) - - if type(rv) == "table" and type(rv.values) == "table" then - local sname, section - for sname, section in pairs(rv.values) do - if comparator(section) then - _, err = call("delete", { - config = config, - section = sname - }) - end - end - end - elseif comparator == nil then - _, err = call("delete", { - config = config, - type = stype - }) - else - return false, "Invalid argument" - end - - return (err == nil), ERRSTR[err] -end diff --git a/modules/luci-base/luasrc/model/uci.luadoc b/modules/luci-base/luasrc/model/uci.luadoc deleted file mode 100644 index 0189d49aa1..0000000000 --- a/modules/luci-base/luasrc/model/uci.luadoc +++ /dev/null @@ -1,369 +0,0 @@ ----[[ -LuCI UCI model library. - -The typical workflow for UCI is: Get a cursor instance from the -cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), -save the changes to the staging area via Cursor.save and finally -Cursor.commit the data to the actual config files. -LuCI then needs to Cursor.apply the changes so daemons etc. are -reloaded. -@cstyle instance -]] -module "luci.model.uci" - ----[[ -Create a new UCI-Cursor. - -@class function -@name cursor -@return UCI-Cursor -]] - ----[[ -Create a new Cursor initialized to the state directory. - -@class function -@name cursor_state -@return UCI cursor -]] - ----[[ -Applies UCI configuration changes. - -If the rollback parameter is set to true, the apply function will invoke the -rollback mechanism which causes the configuration to be automatically reverted -if no confirm() call occurs within a certain timeout. - -The current default timeout is 30s and can be increased using the -"luci.apply.timeout" uci configuration key. - -@class function -@name Cursor.apply -@param rollback Enable rollback mechanism -@return Boolean whether operation succeeded -]] - ----[[ -Confirms UCI apply process. - -If a previous UCI apply with rollback has been invoked using apply(true), -this function confirms the process and cancels the pending rollback timer. - -If no apply with rollback session is active, the function has no effect and -returns with a "No data" error. - -@class function -@name Cursor.confirm -@return Boolean whether operation succeeded -]] - ----[[ -Cancels UCI apply process. - -If a previous UCI apply with rollback has been invoked using apply(true), -this function cancels the process and rolls back the configuration to the -pre-apply state. - -If no apply with rollback session is active, the function has no effect and -returns with a "No data" error. - -@class function -@name Cursor.rollback -@return Boolean whether operation succeeded -]] - ----[[ -Checks whether a pending rollback is scheduled. - -If a previous UCI apply with rollback has been invoked using apply(true), -and has not been confirmed or rolled back yet, this function returns true -and the remaining time until rollback in seconds. If no rollback is pending, -the function returns false. On error, the function returns false and an -additional string describing the error. - -@class function -@name Cursor.rollback_pending -@return Boolean whether rollback is pending -@return Remaining time in seconds -]] - ----[[ -Delete all sections of a given type that match certain criteria. - -@class function -@name Cursor.delete_all -@param config UCI config -@param type UCI section type -@param comparator Function that will be called for each section and returns - a boolean whether to delete the current section (optional) -]] - ----[[ -Create a new section and initialize it with data. - -@class function -@name Cursor.section -@param config UCI config -@param type UCI section type -@param name UCI section name (optional) -@param values Table of key - value pairs to initialize the section with -@return Name of created section -]] - ----[[ -Updated the data of a section using data from a table. - -@class function -@name Cursor.tset -@param config UCI config -@param section UCI section name (optional) -@param values Table of key - value pairs to update the section with -]] - ----[[ -Get a boolean option and return it's value as true or false. - -@class function -@name Cursor.get_bool -@param config UCI config -@param section UCI section name -@param option UCI option -@return Boolean -]] - ----[[ -Get an option or list and return values as table. - -@class function -@name Cursor.get_list -@param config UCI config -@param section UCI section name -@param option UCI option -@return table. If the option was not found, you will simply get an empty - table. -]] - ----[[ -Get the given option from the first section with the given type. - -@class function -@name Cursor.get_first -@param config UCI config -@param type UCI section type -@param option UCI option (optional) -@param default Default value (optional) -@return UCI value -]] - ----[[ -Set given values as list. Setting a list option to an empty list -has the same effect as deleting the option. - -@class function -@name Cursor.set_list -@param config UCI config -@param section UCI section name -@param option UCI option -@param value Value or table. Non-table values will be set as single - item UCI list. -@return Boolean whether operation succeeded -]] - ----[[ -Create a sub-state of this cursor. - -The sub-state is tied to the parent cursor, means it the parent unloads or -loads configs, the sub state will do so as well. - -@class function -@name Cursor.substate -@return UCI state cursor tied to the parent cursor -]] - ----[[ -Add an anonymous section. - -@class function -@name Cursor.add -@param config UCI config -@param type UCI section type -@return Name of created section -]] - ----[[ -Get a table of saved but uncommitted changes. - -@class function -@name Cursor.changes -@param config UCI config -@return Table of changes -@see Cursor.save -]] - ----[[ -Commit saved changes. - -@class function -@name Cursor.commit -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.revert -@see Cursor.save -]] - ----[[ -Deletes a section or an option. - -@class function -@name Cursor.delete -@param config UCI config -@param section UCI section name -@param option UCI option (optional) -@return Boolean whether operation succeeded -]] - ----[[ -Call a function for every section of a certain type. - -@class function -@name Cursor.foreach -@param config UCI config -@param type UCI section type -@param callback Function to be called -@return Boolean whether operation succeeded -]] - ----[[ -Get a section type or an option - -@class function -@name Cursor.get -@param config UCI config -@param section UCI section name -@param option UCI option (optional) -@return UCI value -]] - ----[[ -Get all sections of a config or all values of a section. - -@class function -@name Cursor.get_all -@param config UCI config -@param section UCI section name (optional) -@return Table of UCI sections or table of UCI values -]] - ----[[ -Manually load a config. - -@class function -@name Cursor.load -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.save -@see Cursor.unload -]] - ----[[ -Revert saved but uncommitted changes. - -@class function -@name Cursor.revert -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.commit -@see Cursor.save -]] - ----[[ -Saves changes made to a config to make them committable. - -@class function -@name Cursor.save -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.load -@see Cursor.unload -]] - ----[[ -Set a value or create a named section. - -When invoked with three arguments `config`, `sectionname`, `sectiontype`, -then a named section of the given type is created. - -When invoked with four arguments `config`, `sectionname`, `optionname` and -`optionvalue` then the value of the specified option is set to the given value. - -@class function -@name Cursor.set -@param config UCI config -@param section UCI section name -@param option UCI option or UCI section type -@param value UCI value or nothing if you want to create a section -@return Boolean whether operation succeeded -]] - ----[[ -Get the configuration directory. - -@class function -@name Cursor.get_confdir -@return Configuration directory -]] - ----[[ -Get the directory for uncomitted changes. - -@class function -@name Cursor.get_savedir -@return Save directory -]] - ----[[ -Get the effective session ID. - -@class function -@name Cursor.get_session_id -@return String containing the session ID -]] - ----[[ -Set the configuration directory. - -@class function -@name Cursor.set_confdir -@param directory UCI configuration directory -@return Boolean whether operation succeeded -]] - ----[[ -Set the directory for uncommitted changes. - -@class function -@name Cursor.set_savedir -@param directory UCI changes directory -@return Boolean whether operation succeeded -]] - ----[[ -Set the effective session ID. - -@class function -@name Cursor.set_session_id -@param id String containing the session ID to set -@return Boolean whether operation succeeded -]] - ----[[ -Discard changes made to a config. - -@class function -@name Cursor.unload -@param config UCI config -@return Boolean whether operation succeeded -@see Cursor.load -@see Cursor.save -]] - diff --git a/modules/luci-base/luasrc/sgi/cgi.lua b/modules/luci-base/luasrc/sgi/cgi.lua deleted file mode 100644 index 400db4710d..0000000000 --- a/modules/luci-base/luasrc/sgi/cgi.lua +++ /dev/null @@ -1,73 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -exectime = os.clock() -module("luci.sgi.cgi", package.seeall) -local ltn12 = require("luci.ltn12") -require("nixio.util") -require("luci.http") -require("luci.sys") -require("luci.dispatcher") - --- Limited source to avoid endless blocking -local function limitsource(handle, limit) - limit = limit or 0 - local BLOCKSIZE = ltn12.BLOCKSIZE - - return function() - if limit < 1 then - handle:close() - return nil - else - local read = (limit > BLOCKSIZE) and BLOCKSIZE or limit - limit = limit - read - - local chunk = handle:read(read) - if not chunk then handle:close() end - return chunk - end - end -end - -function run() - local r = luci.http.Request( - luci.sys.getenv(), - limitsource(io.stdin, tonumber(luci.sys.getenv("CONTENT_LENGTH"))), - ltn12.sink.file(io.stderr) - ) - - local x = coroutine.create(luci.dispatcher.httpdispatch) - local hcache = "" - local active = true - - while coroutine.status(x) ~= "dead" do - local res, id, data1, data2 = coroutine.resume(x, r) - - if not res then - print("Status: 500 Internal Server Error") - print("Content-Type: text/plain\n") - print(id) - break; - end - - if active then - if id == 1 then - io.write("Status: " .. tostring(data1) .. " " .. data2 .. "\r\n") - elseif id == 2 then - hcache = hcache .. data1 .. ": " .. data2 .. "\r\n" - elseif id == 3 then - io.write(hcache) - io.write("\r\n") - elseif id == 4 then - io.write(tostring(data1 or "")) - elseif id == 5 then - io.flush() - io.close() - active = false - elseif id == 6 then - data1:copyz(nixio.stdout, data2) - data1:close() - end - end - end -end diff --git a/modules/luci-base/luasrc/sgi/uhttpd.lua b/modules/luci-base/luasrc/sgi/uhttpd.lua deleted file mode 100644 index 4cd3649c62..0000000000 --- a/modules/luci-base/luasrc/sgi/uhttpd.lua +++ /dev/null @@ -1,99 +0,0 @@ --- Copyright 2010 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -require "nixio.util" -require "luci.http" -require "luci.sys" -require "luci.dispatcher" -require "luci.ltn12" - -function handle_request(env) - exectime = os.clock() - local renv = { - CONTENT_LENGTH = env.CONTENT_LENGTH, - CONTENT_TYPE = env.CONTENT_TYPE, - REQUEST_METHOD = env.REQUEST_METHOD, - REQUEST_URI = env.REQUEST_URI, - PATH_INFO = env.PATH_INFO, - SCRIPT_NAME = env.SCRIPT_NAME:gsub("/+$", ""), - SCRIPT_FILENAME = env.SCRIPT_NAME, - SERVER_PROTOCOL = env.SERVER_PROTOCOL, - QUERY_STRING = env.QUERY_STRING, - DOCUMENT_ROOT = env.DOCUMENT_ROOT, - HTTPS = env.HTTPS, - REDIRECT_STATUS = env.REDIRECT_STATUS, - REMOTE_ADDR = env.REMOTE_ADDR, - REMOTE_NAME = env.REMOTE_NAME, - REMOTE_PORT = env.REMOTE_PORT, - REMOTE_USER = env.REMOTE_USER, - SERVER_ADDR = env.SERVER_ADDR, - SERVER_NAME = env.SERVER_NAME, - SERVER_PORT = env.SERVER_PORT - } - - local k, v - for k, v in pairs(env.headers) do - k = k:upper():gsub("%-", "_") - renv["HTTP_" .. k] = v - end - - local len = tonumber(env.CONTENT_LENGTH) or 0 - local function recv() - if len > 0 then - local rlen, rbuf = uhttpd.recv(4096) - if rlen >= 0 then - len = len - rlen - return rbuf - end - end - return nil - end - - local send = uhttpd.send - - local req = luci.http.Request( - renv, recv, luci.ltn12.sink.file(io.stderr) - ) - - - local x = coroutine.create(luci.dispatcher.httpdispatch) - local hcache = { } - local active = true - - while coroutine.status(x) ~= "dead" do - local res, id, data1, data2 = coroutine.resume(x, req) - - if not res then - send("Status: 500 Internal Server Error\r\n") - send("Content-Type: text/plain\r\n\r\n") - send(tostring(id)) - break - end - - if active then - if id == 1 then - send("Status: ") - send(tostring(data1)) - send(" ") - send(tostring(data2)) - send("\r\n") - elseif id == 2 then - hcache[data1] = data2 - elseif id == 3 then - for k, v in pairs(hcache) do - send(tostring(k)) - send(": ") - send(tostring(v)) - send("\r\n") - end - send("\r\n") - elseif id == 4 then - send(tostring(data1 or "")) - elseif id == 5 then - active = false - elseif id == 6 then - data1:copyz(nixio.stdout, data2) - end - end - end -end diff --git a/modules/luci-base/luasrc/store.lua b/modules/luci-base/luasrc/store.lua deleted file mode 100644 index a735981137..0000000000 --- a/modules/luci-base/luasrc/store.lua +++ /dev/null @@ -1,6 +0,0 @@ --- Copyright 2009 Steven Barth --- Copyright 2009 Jo-Philipp Wich --- Licensed to the public under the Apache License 2.0. - -local util = require "luci.util" -module("luci.store", util.threadlocal) diff --git a/modules/luci-base/luasrc/sys.lua b/modules/luci-base/luasrc/sys.lua deleted file mode 100644 index e6eb762e48..0000000000 --- a/modules/luci-base/luasrc/sys.lua +++ /dev/null @@ -1,615 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -local io = require "io" -local os = require "os" -local table = require "table" -local nixio = require "nixio" -local fs = require "nixio.fs" -local uci = require "luci.model.uci" - -local luci = {} -luci.util = require "luci.util" -luci.ip = require "luci.ip" - -local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack = - tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack - - -module "luci.sys" - -function call(...) - return os.execute(...) / 256 -end - -exec = luci.util.exec - --- containing the whole environment is returned otherwise this function returns --- the corresponding string value for the given name or nil if no such variable --- exists. -getenv = nixio.getenv - -function hostname(newname) - if type(newname) == "string" and #newname > 0 then - fs.writefile( "/proc/sys/kernel/hostname", newname ) - return newname - else - return nixio.uname().nodename - end -end - -function httpget(url, stream, target) - if not target then - local source = stream and io.popen or luci.util.exec - return source("wget -qO- %s" % luci.util.shellquote(url)) - else - return os.execute("wget -qO %s %s" % - {luci.util.shellquote(target), luci.util.shellquote(url)}) - end -end - -function reboot() - return os.execute("reboot >/dev/null 2>&1") -end - -function syslog() - return luci.util.exec("logread") -end - -function dmesg() - return luci.util.exec("dmesg") -end - -function uniqueid(bytes) - local rand = fs.readfile("/dev/urandom", bytes) - return rand and nixio.bin.hexlify(rand) -end - -function uptime() - return nixio.sysinfo().uptime -end - - -net = {} - -local function _nethints(what, callback) - local _, k, e, mac, ip, name, duid, iaid - local cur = uci.cursor() - local ifn = { } - local hosts = { } - local lookup = { } - - local function _add(i, ...) - local k = select(i, ...) - if k then - if not hosts[k] then hosts[k] = { } end - hosts[k][1] = select(1, ...) or hosts[k][1] - hosts[k][2] = select(2, ...) or hosts[k][2] - hosts[k][3] = select(3, ...) or hosts[k][3] - hosts[k][4] = select(4, ...) or hosts[k][4] - end - end - - luci.ip.neighbors(nil, function(neigh) - if neigh.mac and neigh.family == 4 then - _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil) - elseif neigh.mac and neigh.family == 6 then - _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil) - end - end) - - if fs.access("/etc/ethers") then - for e in io.lines("/etc/ethers") do - mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)") - mac = luci.ip.checkmac(mac) - if mac and name then - if luci.ip.checkip4(name) then - _add(what, mac, name, nil, nil) - else - _add(what, mac, nil, nil, name) - end - end - end - end - - cur:foreach("dhcp", "dnsmasq", - function(s) - if s.leasefile and fs.access(s.leasefile) then - for e in io.lines(s.leasefile) do - mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)") - mac = luci.ip.checkmac(mac) - if mac and ip then - _add(what, mac, ip, nil, name ~= "*" and name) - end - end - end - end - ) - - cur:foreach("dhcp", "odhcpd", - function(s) - if type(s.leasefile) == "string" and fs.access(s.leasefile) then - for e in io.lines(s.leasefile) do - duid, iaid, name, _, ip = e:match("^# %S+ (%S+) (%S+) (%S+) (-?%d+) %S+ %S+ ([0-9a-f:.]+)/[0-9]+") - mac = net.duid_to_mac(duid) - if mac then - if ip and iaid == "ipv4" then - _add(what, mac, ip, nil, name ~= "*" and name) - elseif ip then - _add(what, mac, nil, ip, name ~= "*" and name) - end - end - end - end - end - ) - - cur:foreach("dhcp", "host", - function(s) - for mac in luci.util.imatch(s.mac) do - mac = luci.ip.checkmac(mac) - if mac then - _add(what, mac, s.ip, nil, s.name) - end - end - end) - - for _, e in ipairs(nixio.getifaddrs()) do - if e.name ~= "lo" then - ifn[e.name] = ifn[e.name] or { } - if e.family == "packet" and e.addr and #e.addr == 17 then - ifn[e.name][1] = e.addr:upper() - elseif e.family == "inet" then - ifn[e.name][2] = e.addr - elseif e.family == "inet6" then - ifn[e.name][3] = e.addr - end - end - end - - for _, e in pairs(ifn) do - if e[what] and (e[2] or e[3]) then - _add(what, e[1], e[2], e[3], e[4]) - end - end - - for _, e in pairs(hosts) do - lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3]) - end - - if #lookup > 0 then - lookup = luci.util.ubus("network.rrdns", "lookup", { - addrs = lookup, - timeout = 250, - limit = 1000 - }) or { } - end - - for _, e in luci.util.kspairs(hosts) do - callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4]) - end -end - --- Each entry contains the values in the following order: --- [ "mac", "name" ] -function net.mac_hints(callback) - if callback then - _nethints(1, function(mac, v4, v6, name) - name = name or v4 - if name and name ~= mac then - callback(mac, name or v4) - end - end) - else - local rv = { } - _nethints(1, function(mac, v4, v6, name) - name = name or v4 - if name and name ~= mac then - rv[#rv+1] = { mac, name or v4 } - end - end) - return rv - end -end - --- Each entry contains the values in the following order: --- [ "ip", "name" ] -function net.ipv4_hints(callback) - if callback then - _nethints(2, function(mac, v4, v6, name) - name = name or mac - if name and name ~= v4 then - callback(v4, name) - end - end) - else - local rv = { } - _nethints(2, function(mac, v4, v6, name) - name = name or mac - if name and name ~= v4 then - rv[#rv+1] = { v4, name } - end - end) - return rv - end -end - --- Each entry contains the values in the following order: --- [ "ip", "name" ] -function net.ipv6_hints(callback) - if callback then - _nethints(3, function(mac, v4, v6, name) - name = name or mac - if name and name ~= v6 then - callback(v6, name) - end - end) - else - local rv = { } - _nethints(3, function(mac, v4, v6, name) - name = name or mac - if name and name ~= v6 then - rv[#rv+1] = { v6, name } - end - end) - return rv - end -end - -function net.host_hints(callback) - if callback then - _nethints(1, function(mac, v4, v6, name) - if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then - callback(mac, v4, v6, name) - end - end) - else - local rv = { } - _nethints(1, function(mac, v4, v6, name) - if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then - local e = { } - if v4 then e.ipv4 = v4 end - if v6 then e.ipv6 = v6 end - if name then e.name = name end - rv[mac] = e - end - end) - return rv - end -end - -function net.conntrack(callback) - local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack") - if not ok or not nfct then - return nil - end - - local line, connt = nil, (not callback) and { } - for line in nfct do - local fam, l3, l4, rest = - line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(.+)$") - - local timeout, tuples = rest:match("^(%d+) +(.+)$") - - if not tuples then - tuples = rest - end - - if fam and l3 and l4 and not tuples:match("^TIME_WAIT ") then - l4 = nixio.getprotobynumber(l4) - - local entry = { - bytes = 0, - packets = 0, - layer3 = fam, - layer4 = l4 and l4.name or "unknown", - timeout = tonumber(timeout, 10) - } - - local key, val - for key, val in tuples:gmatch("(%w+)=(%S+)") do - if key == "bytes" or key == "packets" then - entry[key] = entry[key] + tonumber(val, 10) - elseif key == "src" or key == "dst" then - if entry[key] == nil then - entry[key] = luci.ip.new(val):string() - end - elseif key == "sport" or key == "dport" then - if entry[key] == nil then - entry[key] = val - end - elseif val then - entry[key] = val - end - end - - if callback then - callback(entry) - else - connt[#connt+1] = entry - end - end - end - - return callback and true or connt -end - -function net.devices() - local devs = {} - local seen = {} - for k, v in ipairs(nixio.getifaddrs()) do - if v.name and not seen[v.name] then - seen[v.name] = true - devs[#devs+1] = v.name - end - end - return devs -end - -function net.duid_to_mac(duid) - local b1, b2, b3, b4, b5, b6 - - if type(duid) == "string" then - -- DUID-LLT / Ethernet - if #duid == 28 then - b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$") - - -- DUID-LL / Ethernet - elseif #duid == 20 then - b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$") - - -- DUID-LL / Ethernet (Without Header) - elseif #duid == 12 then - b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$") - end - end - - return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":")) -end - -process = {} - -function process.info(key) - local s = {uid = nixio.getuid(), gid = nixio.getgid()} - return not key and s or s[key] -end - -function process.list() - local data = {} - local k - local ps = luci.util.execi("/bin/busybox top -bn1") - - if not ps then - return - end - - for line in ps do - local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match( - "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][ 2 then - fd:close() - end -end - -function process.exec(command, stdout, stderr, nowait) - local out_r, out_w, err_r, err_w - if stdout then out_r, out_w = nixio.pipe() end - if stderr then err_r, err_w = nixio.pipe() end - - local pid = nixio.fork() - if pid == 0 then - nixio.chdir("/") - - local null = nixio.open("/dev/null", "w+") - if null then - nixio.dup(out_w or null, nixio.stdout) - nixio.dup(err_w or null, nixio.stderr) - nixio.dup(null, nixio.stdin) - xclose(out_w) - xclose(out_r) - xclose(err_w) - xclose(err_r) - xclose(null) - end - - nixio.exec(unpack(command)) - os.exit(-1) - end - - local _, pfds, rv = nil, {}, { code = -1, pid = pid } - - xclose(out_w) - xclose(err_w) - - if out_r then - pfds[#pfds+1] = { - fd = out_r, - cb = type(stdout) == "function" and stdout, - name = "stdout", - events = nixio.poll_flags("in", "err", "hup") - } - end - - if err_r then - pfds[#pfds+1] = { - fd = err_r, - cb = type(stderr) == "function" and stderr, - name = "stderr", - events = nixio.poll_flags("in", "err", "hup") - } - end - - while #pfds > 0 do - local nfds, err = nixio.poll(pfds, -1) - if not nfds and err ~= nixio.const.EINTR then - break - end - - local i - for i = #pfds, 1, -1 do - local rfd = pfds[i] - if rfd.revents > 0 then - local chunk, err = rfd.fd:read(4096) - if chunk and #chunk > 0 then - if rfd.cb then - rfd.cb(chunk) - else - rfd.buf = rfd.buf or {} - rfd.buf[#rfd.buf + 1] = chunk - end - else - table.remove(pfds, i) - if rfd.buf then - rv[rfd.name] = table.concat(rfd.buf, "") - end - rfd.fd:close() - end - end - end - end - - if not nowait then - _, _, rv.code = nixio.waitpid(pid) - end - - return rv -end - - -user = {} - --- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } -user.getuser = nixio.getpw - -function user.getpasswd(username) - local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username) - local pwh = pwe and (pwe.pwdp or pwe.passwd) - if not pwh or #pwh < 1 then - return nil, pwe - else - return pwh, pwe - end -end - -function user.checkpasswd(username, pass) - local pwh, pwe = user.getpasswd(username) - if pwe then - return (pwh == nil or nixio.crypt(pass, pwh) == pwh) - end - return false -end - -function user.setpasswd(username, password) - return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{ - luci.util.shellquote(password), - luci.util.shellquote(password), - luci.util.shellquote(username) - }) -end - - -wifi = {} - -function wifi.getiwinfo(ifname) - local ntm = require "luci.model.network" - - ntm.init() - - local wnet = ntm:get_wifinet(ifname) - if wnet and wnet.iwinfo then - return wnet.iwinfo - end - - local wdev = ntm:get_wifidev(ifname) - if wdev and wdev.iwinfo then - return wdev.iwinfo - end - - return { ifname = ifname } -end - - -init = {} -init.dir = "/etc/init.d/" - -function init.names() - local names = { } - for name in fs.glob(init.dir.."*") do - names[#names+1] = fs.basename(name) - end - return names -end - -function init.index(name) - name = fs.basename(name) - if fs.access(init.dir..name) then - return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null" - %{ init.dir, name }) - end -end - -local function init_action(action, name) - name = fs.basename(name) - if fs.access(init.dir..name) then - return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action }) - end -end - -function init.enabled(name) - return (init_action("enabled", name) == 0) -end - -function init.enable(name) - return (init_action("enable", name) == 0) -end - -function init.disable(name) - return (init_action("disable", name) == 0) -end - -function init.start(name) - return (init_action("start", name) == 0) -end - -function init.stop(name) - return (init_action("stop", name) == 0) -end - -function init.restart(name) - return (init_action("restart", name) == 0) -end - -function init.reload(name) - return (init_action("reload", name) == 0) -end diff --git a/modules/luci-base/luasrc/sys.luadoc b/modules/luci-base/luasrc/sys.luadoc deleted file mode 100644 index c1e088eb23..0000000000 --- a/modules/luci-base/luasrc/sys.luadoc +++ /dev/null @@ -1,392 +0,0 @@ ----[[ -LuCI Linux and POSIX system utilities. -]] -module "luci.sys" - ----[[ -Execute a given shell command and return the error code - -@class function -@name call -@param ... Command to call -@return Error code of the command -]] - ----[[ -Execute a given shell command and capture its standard output - -@class function -@name exec -@param command Command to call -@return String containing the return the output of the command -]] - ----[[ -Retrieve information about currently mounted file systems. - -@class function -@name mounts -@return Table containing mount information -]] - ----[[ -Retrieve environment variables. If no variable is given then a table - -containing the whole environment is returned otherwise this function returns -the corresponding string value for the given name or nil if no such variable -exists. -@class function -@name getenv -@param var Name of the environment variable to retrieve (optional) -@return String containing the value of the specified variable -@return Table containing all variables if no variable name is given -]] - ----[[ -Get or set the current hostname. - -@class function -@name hostname -@param String containing a new hostname to set (optional) -@return String containing the system hostname -]] - ----[[ -Returns the contents of a documented referred by an URL. - -@class function -@name httpget -@param url The URL to retrieve -@param stream Return a stream instead of a buffer -@param target Directly write to target file name -@return String containing the contents of given the URL -]] - ----[[ -Initiate a system reboot. - -@class function -@name reboot -@return Return value of os.execute() -]] - ----[[ -Retrieves the output of the "logread" command. - -@class function -@name syslog -@return String containing the current log buffer -]] - ----[[ -Retrieves the output of the "dmesg" command. - -@class function -@name dmesg -@return String containing the current log buffer -]] - ----[[ -Generates a random id with specified length. - -@class function -@name uniqueid -@param bytes Number of bytes for the unique id -@return String containing hex encoded id -]] - ----[[ -Returns the current system uptime stats. - -@class function -@name uptime -@return String containing total uptime in seconds -]] - ----[[ -LuCI system utilities / network related functions. - -@class module -@name luci.sys.net -]] - ----[[ -Returns a two-dimensional table of mac address hints. - -@class function -@name net.mac_hints -@return Table of table containing known hosts from various sources. - Each entry contains the values in the following order: - [ "mac", "name" ] -]] - ----[[ -Returns a two-dimensional table of IPv4 address hints. - -@class function -@name net.ipv4_hints -@return Table of table containing known hosts from various sources. - Each entry contains the values in the following order: - [ "ip", "name" ] -]] - ----[[ -Returns a two-dimensional table of IPv6 address hints. - -@class function -@name net.ipv6_hints -@return Table of table containing known hosts from various sources. - Each entry contains the values in the following order: - [ "ip", "name" ] -]] - ----[[ -Returns a two-dimensional table of host hints. - -@class function -@name net.host_hints -@return Table of table containing known hosts from various sources, - indexed by mac address. Each subtable contains at least one - of the fields "name", "ipv4" or "ipv6". -]] - ----[[ -Returns conntrack information - -@class function -@name net.conntrack -@return Table with the currently tracked IP connections -]] - ----[[ -Determine the names of available network interfaces. - -@class function -@name net.devices -@return Table containing all current interface names -]] - ----[[ -LuCI system utilities / process related functions. - -@class module -@name luci.sys.process -]] - ----[[ -Get the current process id. - -@class function -@name process.info -@return Number containing the current pid -]] - ----[[ -Retrieve information about currently running processes. - -@class function -@name process.list -@return Table containing process information -]] - ----[[ -Set the gid of a process identified by given pid. - -@class function -@name process.setgroup -@param gid Number containing the Unix group id -@return Boolean indicating successful operation -@return String containing the error message if failed -@return Number containing the error code if failed -]] - ----[[ -Set the uid of a process identified by given pid. - -@class function -@name process.setuser -@param uid Number containing the Unix user id -@return Boolean indicating successful operation -@return String containing the error message if failed -@return Number containing the error code if failed -]] - ----[[ -Send a signal to a process identified by given pid. - -@class function -@name process.signal -@param pid Number containing the process id -@param sig Signal to send (default: 15 [SIGTERM]) -@return Boolean indicating successful operation -@return Number containing the error code if failed -]] - ----[[ -Execute a process, optionally capturing stdio. - -Executes the process specified by the given argv vector, e.g. -`{ "/bin/sh", "-c", "echo 1" }` and waits for it to terminate unless a true -value has been passed for the "nowait" parameter. - -When a function value is passed for the stdout or stderr arguments, the passed -function is repeatedly called for each chunk read from the corresponding stdio -stream. The read data is passed as string containing at most 4096 bytes at a -time. - -When a true, non-function value is passed for the stdout or stderr arguments, -the data of the corresponding stdio stream is read into an internal string -buffer and returned as "stdout" or "stderr" field respectively in the result -table. - -When a true value is passed to the nowait parameter, the function does not -await process termination but returns as soon as all captured stdio streams -have been closed or - if no streams are captured - immediately after launching -the process. - -@class function -@name process.exec -@param commend Table containing the argv vector to execute -@param stdout Callback function or boolean to indicate capturing (optional) -@param stderr Callback function or boolean to indicate capturing (optional) -@param nowait Don't wait for process termination when true (optional) -@return Table containing at least the fields "code" which holds the exit - status of the invoked process or "-1" on error and "pid", which - contains the process id assigned to the spawned process. When - stdout and/or stderr capturing has been requested, it additionally - contains "stdout" and "stderr" fields respectively, holding the - captured stdio data as string. -]] - ----[[ -LuCI system utilities / user related functions. - -@class module -@name luci.sys.user -]] - ----[[ -Retrieve user information for given uid. - -@class function -@name getuser -@param uid Number containing the Unix user id -@return Table containing the following fields: --- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } -]] - ----[[ -Retrieve the current user password hash. - -@class function -@name user.getpasswd -@param username String containing the username to retrieve the password for -@return String containing the hash or nil if no password is set. -@return Password database entry -]] - ----[[ -Test whether given string matches the password of a given system user. - -@class function -@name user.checkpasswd -@param username String containing the Unix user name -@param pass String containing the password to compare -@return Boolean indicating whether the passwords are equal -]] - ----[[ -Change the password of given user. - -@class function -@name user.setpasswd -@param username String containing the Unix user name -@param password String containing the password to compare -@return Number containing 0 on success and >= 1 on error -]] - ----[[ -LuCI system utilities / wifi related functions. - -@class module -@name luci.sys.wifi -]] - ----[[ -Get wireless information for given interface. - -@class function -@name wifi.getiwinfo -@param ifname String containing the interface name -@return A wrapped iwinfo object instance -]] - ----[[ -LuCI system utilities / init related functions. - -@class module -@name luci.sys.init -]] - ----[[ -Get the names of all installed init scripts - -@class function -@name init.names -@return Table containing the names of all inistalled init scripts -]] - ----[[ -Get the index of he given init script - -@class function -@name init.index -@param name Name of the init script -@return Numeric index value -]] - ----[[ -Test whether the given init script is enabled - -@class function -@name init.enabled -@param name Name of the init script -@return Boolean indicating whether init is enabled -]] - ----[[ -Enable the given init script - -@class function -@name init.enable -@param name Name of the init script -@return Boolean indicating success -]] - ----[[ -Disable the given init script - -@class function -@name init.disable -@param name Name of the init script -@return Boolean indicating success -]] - ----[[ -Start the given init script - -@class function -@name init.start -@param name Name of the init script -@return Boolean indicating success -]] - ----[[ -Stop the given init script - -@class function -@name init.stop -@param name Name of the init script -@return Boolean indicating success -]] - diff --git a/modules/luci-base/luasrc/sys/zoneinfo.lua b/modules/luci-base/luasrc/sys/zoneinfo.lua deleted file mode 100644 index aa054a246f..0000000000 --- a/modules/luci-base/luasrc/sys/zoneinfo.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Licensed to the public under the Apache License 2.0. - -local setmetatable, require, rawget, rawset = setmetatable, require, rawget, rawset - -module "luci.sys.zoneinfo" - -setmetatable(_M, { - __index = function(t, k) - if k == "TZ" and not rawget(t, k) then - local m = require "luci.sys.zoneinfo.tzdata" - rawset(t, k, rawget(m, k)) - elseif k == "OFFSET" and not rawget(t, k) then - local m = require "luci.sys.zoneinfo.tzoffset" - rawset(t, k, rawget(m, k)) - end - - return rawget(t, k) - end -}) diff --git a/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua b/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua deleted file mode 100644 index 3ef2f4caf4..0000000000 --- a/modules/luci-base/luasrc/sys/zoneinfo/tzdata.lua +++ /dev/null @@ -1,455 +0,0 @@ --- Licensed to the public under the Apache License 2.0. - -module "luci.sys.zoneinfo.tzdata" - -TZ = { - { 'Africa/Abidjan', 'GMT0' }, - { 'Africa/Accra', 'GMT0' }, - { 'Africa/Addis Ababa', 'EAT-3' }, - { 'Africa/Algiers', 'CET-1' }, - { 'Africa/Asmara', 'EAT-3' }, - { 'Africa/Bamako', 'GMT0' }, - { 'Africa/Bangui', 'WAT-1' }, - { 'Africa/Banjul', 'GMT0' }, - { 'Africa/Bissau', 'GMT0' }, - { 'Africa/Blantyre', 'CAT-2' }, - { 'Africa/Brazzaville', 'WAT-1' }, - { 'Africa/Bujumbura', 'CAT-2' }, - { 'Africa/Cairo', 'EET-2' }, - { 'Africa/Casablanca', '<+01>-1' }, - { 'Africa/Ceuta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Africa/Conakry', 'GMT0' }, - { 'Africa/Dakar', 'GMT0' }, - { 'Africa/Dar es Salaam', 'EAT-3' }, - { 'Africa/Djibouti', 'EAT-3' }, - { 'Africa/Douala', 'WAT-1' }, - { 'Africa/El Aaiun', '<+01>-1' }, - { 'Africa/Freetown', 'GMT0' }, - { 'Africa/Gaborone', 'CAT-2' }, - { 'Africa/Harare', 'CAT-2' }, - { 'Africa/Johannesburg', 'SAST-2' }, - { 'Africa/Juba', 'CAT-2' }, - { 'Africa/Kampala', 'EAT-3' }, - { 'Africa/Khartoum', 'CAT-2' }, - { 'Africa/Kigali', 'CAT-2' }, - { 'Africa/Kinshasa', 'WAT-1' }, - { 'Africa/Lagos', 'WAT-1' }, - { 'Africa/Libreville', 'WAT-1' }, - { 'Africa/Lome', 'GMT0' }, - { 'Africa/Luanda', 'WAT-1' }, - { 'Africa/Lubumbashi', 'CAT-2' }, - { 'Africa/Lusaka', 'CAT-2' }, - { 'Africa/Malabo', 'WAT-1' }, - { 'Africa/Maputo', 'CAT-2' }, - { 'Africa/Maseru', 'SAST-2' }, - { 'Africa/Mbabane', 'SAST-2' }, - { 'Africa/Mogadishu', 'EAT-3' }, - { 'Africa/Monrovia', 'GMT0' }, - { 'Africa/Nairobi', 'EAT-3' }, - { 'Africa/Ndjamena', 'WAT-1' }, - { 'Africa/Niamey', 'WAT-1' }, - { 'Africa/Nouakchott', 'GMT0' }, - { 'Africa/Ouagadougou', 'GMT0' }, - { 'Africa/Porto-Novo', 'WAT-1' }, - { 'Africa/Sao Tome', 'GMT0' }, - { 'Africa/Tripoli', 'EET-2' }, - { 'Africa/Tunis', 'CET-1' }, - { 'Africa/Windhoek', 'CAT-2' }, - { 'America/Adak', 'HST10HDT,M3.2.0,M11.1.0' }, - { 'America/Anchorage', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Anguilla', 'AST4' }, - { 'America/Antigua', 'AST4' }, - { 'America/Araguaina', '<-03>3' }, - { 'America/Argentina/Buenos Aires', '<-03>3' }, - { 'America/Argentina/Catamarca', '<-03>3' }, - { 'America/Argentina/Cordoba', '<-03>3' }, - { 'America/Argentina/Jujuy', '<-03>3' }, - { 'America/Argentina/La Rioja', '<-03>3' }, - { 'America/Argentina/Mendoza', '<-03>3' }, - { 'America/Argentina/Rio Gallegos', '<-03>3' }, - { 'America/Argentina/Salta', '<-03>3' }, - { 'America/Argentina/San Juan', '<-03>3' }, - { 'America/Argentina/San Luis', '<-03>3' }, - { 'America/Argentina/Tucuman', '<-03>3' }, - { 'America/Argentina/Ushuaia', '<-03>3' }, - { 'America/Aruba', 'AST4' }, - { 'America/Asuncion', '<-04>4<-03>,M10.1.0/0,M3.4.0/0' }, - { 'America/Atikokan', 'EST5' }, - { 'America/Bahia', '<-03>3' }, - { 'America/Bahia Banderas', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Barbados', 'AST4' }, - { 'America/Belem', '<-03>3' }, - { 'America/Belize', 'CST6' }, - { 'America/Blanc-Sablon', 'AST4' }, - { 'America/Boa Vista', '<-04>4' }, - { 'America/Bogota', '<-05>5' }, - { 'America/Boise', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Cambridge Bay', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Campo Grande', '<-04>4' }, - { 'America/Cancun', 'EST5' }, - { 'America/Caracas', '<-04>4' }, - { 'America/Cayenne', '<-03>3' }, - { 'America/Cayman', 'EST5' }, - { 'America/Chicago', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Chihuahua', 'MST7MDT,M4.1.0,M10.5.0' }, - { 'America/Costa Rica', 'CST6' }, - { 'America/Creston', 'MST7' }, - { 'America/Cuiaba', '<-04>4' }, - { 'America/Curacao', 'AST4' }, - { 'America/Danmarkshavn', 'GMT0' }, - { 'America/Dawson', 'MST7' }, - { 'America/Dawson Creek', 'MST7' }, - { 'America/Denver', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Detroit', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Dominica', 'AST4' }, - { 'America/Edmonton', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Eirunepe', '<-05>5' }, - { 'America/El Salvador', 'CST6' }, - { 'America/Fort Nelson', 'MST7' }, - { 'America/Fortaleza', '<-03>3' }, - { 'America/Glace Bay', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Goose Bay', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Grand Turk', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Grenada', 'AST4' }, - { 'America/Guadeloupe', 'AST4' }, - { 'America/Guatemala', 'CST6' }, - { 'America/Guayaquil', '<-05>5' }, - { 'America/Guyana', '<-04>4' }, - { 'America/Halifax', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Havana', 'CST5CDT,M3.2.0/0,M11.1.0/1' }, - { 'America/Hermosillo', 'MST7' }, - { 'America/Indiana/Indianapolis', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Knox', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Marengo', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Petersburg', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Tell City', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Vevay', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Vincennes', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Indiana/Winamac', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Inuvik', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Iqaluit', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Jamaica', 'EST5' }, - { 'America/Juneau', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Kentucky/Louisville', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Kentucky/Monticello', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Kralendijk', 'AST4' }, - { 'America/La Paz', '<-04>4' }, - { 'America/Lima', '<-05>5' }, - { 'America/Los Angeles', 'PST8PDT,M3.2.0,M11.1.0' }, - { 'America/Lower Princes', 'AST4' }, - { 'America/Maceio', '<-03>3' }, - { 'America/Managua', 'CST6' }, - { 'America/Manaus', '<-04>4' }, - { 'America/Marigot', 'AST4' }, - { 'America/Martinique', 'AST4' }, - { 'America/Matamoros', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Mazatlan', 'MST7MDT,M4.1.0,M10.5.0' }, - { 'America/Menominee', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Merida', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Metlakatla', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Mexico City', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Miquelon', '<-03>3<-02>,M3.2.0,M11.1.0' }, - { 'America/Moncton', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Monterrey', 'CST6CDT,M4.1.0,M10.5.0' }, - { 'America/Montevideo', '<-03>3' }, - { 'America/Montserrat', 'AST4' }, - { 'America/Nassau', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/New York', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Nipigon', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Nome', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Noronha', '<-02>2' }, - { 'America/North Dakota/Beulah', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/North Dakota/Center', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/North Dakota/New Salem', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Nuuk', '<-03>3<-02>,M3.5.0/-2,M10.5.0/-1' }, - { 'America/Ojinaga', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'America/Panama', 'EST5' }, - { 'America/Pangnirtung', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Paramaribo', '<-03>3' }, - { 'America/Phoenix', 'MST7' }, - { 'America/Port of Spain', 'AST4' }, - { 'America/Port-au-Prince', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Porto Velho', '<-04>4' }, - { 'America/Puerto Rico', 'AST4' }, - { 'America/Punta Arenas', '<-03>3' }, - { 'America/Rainy River', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Rankin Inlet', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Recife', '<-03>3' }, - { 'America/Regina', 'CST6' }, - { 'America/Resolute', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Rio Branco', '<-05>5' }, - { 'America/Santarem', '<-03>3' }, - { 'America/Santiago', '<-04>4<-03>,M9.1.6/24,M4.1.6/24' }, - { 'America/Santo Domingo', 'AST4' }, - { 'America/Sao Paulo', '<-03>3' }, - { 'America/Scoresbysund', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' }, - { 'America/Sitka', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/St Barthelemy', 'AST4' }, - { 'America/St Johns', 'NST3:30NDT,M3.2.0,M11.1.0' }, - { 'America/St Kitts', 'AST4' }, - { 'America/St Lucia', 'AST4' }, - { 'America/St Thomas', 'AST4' }, - { 'America/St Vincent', 'AST4' }, - { 'America/Swift Current', 'CST6' }, - { 'America/Tegucigalpa', 'CST6' }, - { 'America/Thule', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'America/Thunder Bay', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Tijuana', 'PST8PDT,M3.2.0,M11.1.0' }, - { 'America/Toronto', 'EST5EDT,M3.2.0,M11.1.0' }, - { 'America/Tortola', 'AST4' }, - { 'America/Vancouver', 'PST8PDT,M3.2.0,M11.1.0' }, - { 'America/Whitehorse', 'MST7' }, - { 'America/Winnipeg', 'CST6CDT,M3.2.0,M11.1.0' }, - { 'America/Yakutat', 'AKST9AKDT,M3.2.0,M11.1.0' }, - { 'America/Yellowknife', 'MST7MDT,M3.2.0,M11.1.0' }, - { 'Antarctica/Casey', '<+11>-11' }, - { 'Antarctica/Davis', '<+07>-7' }, - { 'Antarctica/DumontDUrville', '<+10>-10' }, - { 'Antarctica/Macquarie', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, - { 'Antarctica/Mawson', '<+05>-5' }, - { 'Antarctica/McMurdo', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, - { 'Antarctica/Palmer', '<-03>3' }, - { 'Antarctica/Rothera', '<-03>3' }, - { 'Antarctica/Syowa', '<+03>-3' }, - { 'Antarctica/Troll', '<+00>0<+02>-2,M3.5.0/1,M10.5.0/3' }, - { 'Antarctica/Vostok', '<+06>-6' }, - { 'Arctic/Longyearbyen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Asia/Aden', '<+03>-3' }, - { 'Asia/Almaty', '<+06>-6' }, - { 'Asia/Amman', '<+03>-3' }, - { 'Asia/Anadyr', '<+12>-12' }, - { 'Asia/Aqtau', '<+05>-5' }, - { 'Asia/Aqtobe', '<+05>-5' }, - { 'Asia/Ashgabat', '<+05>-5' }, - { 'Asia/Atyrau', '<+05>-5' }, - { 'Asia/Baghdad', '<+03>-3' }, - { 'Asia/Bahrain', '<+03>-3' }, - { 'Asia/Baku', '<+04>-4' }, - { 'Asia/Bangkok', '<+07>-7' }, - { 'Asia/Barnaul', '<+07>-7' }, - { 'Asia/Beirut', 'EET-2EEST,M3.5.0/0,M10.5.0/0' }, - { 'Asia/Bishkek', '<+06>-6' }, - { 'Asia/Brunei', '<+08>-8' }, - { 'Asia/Chita', '<+09>-9' }, - { 'Asia/Choibalsan', '<+08>-8' }, - { 'Asia/Colombo', '<+0530>-5:30' }, - { 'Asia/Damascus', '<+03>-3' }, - { 'Asia/Dhaka', '<+06>-6' }, - { 'Asia/Dili', '<+09>-9' }, - { 'Asia/Dubai', '<+04>-4' }, - { 'Asia/Dushanbe', '<+05>-5' }, - { 'Asia/Famagusta', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Asia/Gaza', 'EET-2EEST,M3.4.4/50,M10.4.4/50' }, - { 'Asia/Hebron', 'EET-2EEST,M3.4.4/50,M10.4.4/50' }, - { 'Asia/Ho Chi Minh', '<+07>-7' }, - { 'Asia/Hong Kong', 'HKT-8' }, - { 'Asia/Hovd', '<+07>-7' }, - { 'Asia/Irkutsk', '<+08>-8' }, - { 'Asia/Jakarta', 'WIB-7' }, - { 'Asia/Jayapura', 'WIT-9' }, - { 'Asia/Jerusalem', 'IST-2IDT,M3.4.4/26,M10.5.0' }, - { 'Asia/Kabul', '<+0430>-4:30' }, - { 'Asia/Kamchatka', '<+12>-12' }, - { 'Asia/Karachi', 'PKT-5' }, - { 'Asia/Kathmandu', '<+0545>-5:45' }, - { 'Asia/Khandyga', '<+09>-9' }, - { 'Asia/Kolkata', 'IST-5:30' }, - { 'Asia/Krasnoyarsk', '<+07>-7' }, - { 'Asia/Kuala Lumpur', '<+08>-8' }, - { 'Asia/Kuching', '<+08>-8' }, - { 'Asia/Kuwait', '<+03>-3' }, - { 'Asia/Macau', 'CST-8' }, - { 'Asia/Magadan', '<+11>-11' }, - { 'Asia/Makassar', 'WITA-8' }, - { 'Asia/Manila', 'PST-8' }, - { 'Asia/Muscat', '<+04>-4' }, - { 'Asia/Nicosia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Asia/Novokuznetsk', '<+07>-7' }, - { 'Asia/Novosibirsk', '<+07>-7' }, - { 'Asia/Omsk', '<+06>-6' }, - { 'Asia/Oral', '<+05>-5' }, - { 'Asia/Phnom Penh', '<+07>-7' }, - { 'Asia/Pontianak', 'WIB-7' }, - { 'Asia/Pyongyang', 'KST-9' }, - { 'Asia/Qatar', '<+03>-3' }, - { 'Asia/Qostanay', '<+06>-6' }, - { 'Asia/Qyzylorda', '<+05>-5' }, - { 'Asia/Riyadh', '<+03>-3' }, - { 'Asia/Sakhalin', '<+11>-11' }, - { 'Asia/Samarkand', '<+05>-5' }, - { 'Asia/Seoul', 'KST-9' }, - { 'Asia/Shanghai', 'CST-8' }, - { 'Asia/Singapore', '<+08>-8' }, - { 'Asia/Srednekolymsk', '<+11>-11' }, - { 'Asia/Taipei', 'CST-8' }, - { 'Asia/Tashkent', '<+05>-5' }, - { 'Asia/Tbilisi', '<+04>-4' }, - { 'Asia/Tehran', '<+0330>-3:30' }, - { 'Asia/Thimphu', '<+06>-6' }, - { 'Asia/Tokyo', 'JST-9' }, - { 'Asia/Tomsk', '<+07>-7' }, - { 'Asia/Ulaanbaatar', '<+08>-8' }, - { 'Asia/Urumqi', '<+06>-6' }, - { 'Asia/Ust-Nera', '<+10>-10' }, - { 'Asia/Vientiane', '<+07>-7' }, - { 'Asia/Vladivostok', '<+10>-10' }, - { 'Asia/Yakutsk', '<+09>-9' }, - { 'Asia/Yangon', '<+0630>-6:30' }, - { 'Asia/Yekaterinburg', '<+05>-5' }, - { 'Asia/Yerevan', '<+04>-4' }, - { 'Atlantic/Azores', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' }, - { 'Atlantic/Bermuda', 'AST4ADT,M3.2.0,M11.1.0' }, - { 'Atlantic/Canary', 'WET0WEST,M3.5.0/1,M10.5.0' }, - { 'Atlantic/Cape Verde', '<-01>1' }, - { 'Atlantic/Faroe', 'WET0WEST,M3.5.0/1,M10.5.0' }, - { 'Atlantic/Madeira', 'WET0WEST,M3.5.0/1,M10.5.0' }, - { 'Atlantic/Reykjavik', 'GMT0' }, - { 'Atlantic/South Georgia', '<-02>2' }, - { 'Atlantic/St Helena', 'GMT0' }, - { 'Atlantic/Stanley', '<-03>3' }, - { 'Australia/Adelaide', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' }, - { 'Australia/Brisbane', 'AEST-10' }, - { 'Australia/Broken Hill', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' }, - { 'Australia/Darwin', 'ACST-9:30' }, - { 'Australia/Eucla', '<+0845>-8:45' }, - { 'Australia/Hobart', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, - { 'Australia/Lindeman', 'AEST-10' }, - { 'Australia/Lord Howe', '<+1030>-10:30<+11>-11,M10.1.0,M4.1.0' }, - { 'Australia/Melbourne', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, - { 'Australia/Perth', 'AWST-8' }, - { 'Australia/Sydney', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, - { 'Etc/GMT', 'GMT0' }, - { 'Etc/GMT+1', '<-01>1' }, - { 'Etc/GMT+10', '<-10>10' }, - { 'Etc/GMT+11', '<-11>11' }, - { 'Etc/GMT+12', '<-12>12' }, - { 'Etc/GMT+2', '<-02>2' }, - { 'Etc/GMT+3', '<-03>3' }, - { 'Etc/GMT+4', '<-04>4' }, - { 'Etc/GMT+5', '<-05>5' }, - { 'Etc/GMT+6', '<-06>6' }, - { 'Etc/GMT+7', '<-07>7' }, - { 'Etc/GMT+8', '<-08>8' }, - { 'Etc/GMT+9', '<-09>9' }, - { 'Etc/GMT-1', '<+01>-1' }, - { 'Etc/GMT-10', '<+10>-10' }, - { 'Etc/GMT-11', '<+11>-11' }, - { 'Etc/GMT-12', '<+12>-12' }, - { 'Etc/GMT-13', '<+13>-13' }, - { 'Etc/GMT-14', '<+14>-14' }, - { 'Etc/GMT-2', '<+02>-2' }, - { 'Etc/GMT-3', '<+03>-3' }, - { 'Etc/GMT-4', '<+04>-4' }, - { 'Etc/GMT-5', '<+05>-5' }, - { 'Etc/GMT-6', '<+06>-6' }, - { 'Etc/GMT-7', '<+07>-7' }, - { 'Etc/GMT-8', '<+08>-8' }, - { 'Etc/GMT-9', '<+09>-9' }, - { 'Europe/Amsterdam', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Andorra', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Astrakhan', '<+04>-4' }, - { 'Europe/Athens', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Belgrade', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Berlin', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Bratislava', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Brussels', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Bucharest', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Budapest', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Busingen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Chisinau', 'EET-2EEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Copenhagen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Dublin', 'IST-1GMT0,M10.5.0,M3.5.0/1' }, - { 'Europe/Gibraltar', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Guernsey', 'GMT0BST,M3.5.0/1,M10.5.0' }, - { 'Europe/Helsinki', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Isle of Man', 'GMT0BST,M3.5.0/1,M10.5.0' }, - { 'Europe/Istanbul', '<+03>-3' }, - { 'Europe/Jersey', 'GMT0BST,M3.5.0/1,M10.5.0' }, - { 'Europe/Kaliningrad', 'EET-2' }, - { 'Europe/Kirov', '<+03>-3' }, - { 'Europe/Kyiv', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Lisbon', 'WET0WEST,M3.5.0/1,M10.5.0' }, - { 'Europe/Ljubljana', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/London', 'GMT0BST,M3.5.0/1,M10.5.0' }, - { 'Europe/Luxembourg', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Madrid', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Malta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Mariehamn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Minsk', '<+03>-3' }, - { 'Europe/Monaco', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Moscow', 'MSK-3' }, - { 'Europe/Oslo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Paris', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Podgorica', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Prague', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Riga', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Rome', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Samara', '<+04>-4' }, - { 'Europe/San Marino', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Sarajevo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Saratov', '<+04>-4' }, - { 'Europe/Simferopol', 'MSK-3' }, - { 'Europe/Skopje', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Sofia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Stockholm', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Tallinn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Tirane', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Ulyanovsk', '<+04>-4' }, - { 'Europe/Vaduz', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Vatican', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Vienna', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Vilnius', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, - { 'Europe/Volgograd', '<+03>-3' }, - { 'Europe/Warsaw', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Zagreb', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Europe/Zurich', 'CET-1CEST,M3.5.0,M10.5.0/3' }, - { 'Indian/Antananarivo', 'EAT-3' }, - { 'Indian/Chagos', '<+06>-6' }, - { 'Indian/Christmas', '<+07>-7' }, - { 'Indian/Cocos', '<+0630>-6:30' }, - { 'Indian/Comoro', 'EAT-3' }, - { 'Indian/Kerguelen', '<+05>-5' }, - { 'Indian/Mahe', '<+04>-4' }, - { 'Indian/Maldives', '<+05>-5' }, - { 'Indian/Mauritius', '<+04>-4' }, - { 'Indian/Mayotte', 'EAT-3' }, - { 'Indian/Reunion', '<+04>-4' }, - { 'Pacific/Apia', '<+13>-13' }, - { 'Pacific/Auckland', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, - { 'Pacific/Bougainville', '<+11>-11' }, - { 'Pacific/Chatham', '<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45' }, - { 'Pacific/Chuuk', '<+10>-10' }, - { 'Pacific/Easter', '<-06>6<-05>,M9.1.6/22,M4.1.6/22' }, - { 'Pacific/Efate', '<+11>-11' }, - { 'Pacific/Fakaofo', '<+13>-13' }, - { 'Pacific/Fiji', '<+12>-12<+13>,M11.2.0,M1.2.3/99' }, - { 'Pacific/Funafuti', '<+12>-12' }, - { 'Pacific/Galapagos', '<-06>6' }, - { 'Pacific/Gambier', '<-09>9' }, - { 'Pacific/Guadalcanal', '<+11>-11' }, - { 'Pacific/Guam', 'ChST-10' }, - { 'Pacific/Honolulu', 'HST10' }, - { 'Pacific/Kanton', '<+13>-13' }, - { 'Pacific/Kiritimati', '<+14>-14' }, - { 'Pacific/Kosrae', '<+11>-11' }, - { 'Pacific/Kwajalein', '<+12>-12' }, - { 'Pacific/Majuro', '<+12>-12' }, - { 'Pacific/Marquesas', '<-0930>9:30' }, - { 'Pacific/Midway', 'SST11' }, - { 'Pacific/Nauru', '<+12>-12' }, - { 'Pacific/Niue', '<-11>11' }, - { 'Pacific/Norfolk', '<+11>-11<+12>,M10.1.0,M4.1.0/3' }, - { 'Pacific/Noumea', '<+11>-11' }, - { 'Pacific/Pago Pago', 'SST11' }, - { 'Pacific/Palau', '<+09>-9' }, - { 'Pacific/Pitcairn', '<-08>8' }, - { 'Pacific/Pohnpei', '<+11>-11' }, - { 'Pacific/Port Moresby', '<+10>-10' }, - { 'Pacific/Rarotonga', '<-10>10' }, - { 'Pacific/Saipan', 'ChST-10' }, - { 'Pacific/Tahiti', '<-10>10' }, - { 'Pacific/Tarawa', '<+12>-12' }, - { 'Pacific/Tongatapu', '<+13>-13' }, - { 'Pacific/Wake', '<+12>-12' }, - { 'Pacific/Wallis', '<+12>-12' }, -} diff --git a/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua b/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua deleted file mode 100644 index caee1d2c1c..0000000000 --- a/modules/luci-base/luasrc/sys/zoneinfo/tzoffset.lua +++ /dev/null @@ -1,46 +0,0 @@ --- Licensed to the public under the Apache License 2.0. - -module "luci.sys.zoneinfo.tzoffset" - -OFFSET = { - gmt = 0, -- GMT - eat = 10800, -- EAT - cet = 3600, -- CET - wat = 3600, -- WAT - cat = 7200, -- CAT - eet = 7200, -- EET - sast = 7200, -- SAST - hst = -36000, -- HST - hdt = -32400, -- HDT - akst = -32400, -- AKST - akdt = -28800, -- AKDT - ast = -14400, -- AST - est = -18000, -- EST - cst = -21600, -- CST - cdt = -18000, -- CDT - mst = -25200, -- MST - mdt = -21600, -- MDT - pst = -28800, -- PST - pdt = -25200, -- PDT - nst = -12600, -- NST - ndt = -9000, -- NDT - aest = 36000, -- AEST - aedt = 39600, -- AEDT - nzst = 43200, -- NZST - nzdt = 46800, -- NZDT - hkt = 28800, -- HKT - wib = 25200, -- WIB - wit = 32400, -- WIT - ist = 7200, -- IST - idt = 10800, -- IDT - pkt = 18000, -- PKT - wita = 28800, -- WITA - kst = 32400, -- KST - jst = 32400, -- JST - wet = 0, -- WET - acst = 34200, -- ACST - acdt = 37800, -- ACDT - awst = 28800, -- AWST - msk = 10800, -- MSK - sst = -39600, -- SST -} diff --git a/modules/luci-base/luasrc/template.lua b/modules/luci-base/luasrc/template.lua deleted file mode 100644 index 3955bd76f3..0000000000 --- a/modules/luci-base/luasrc/template.lua +++ /dev/null @@ -1,100 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -local util = require "luci.util" -local config = require "luci.config" -local tparser = require "luci.template.parser" - -local tostring, pairs, loadstring = tostring, pairs, loadstring -local setmetatable, loadfile = setmetatable, loadfile -local getfenv, setfenv, rawget = getfenv, setfenv, rawget -local assert, type, error = assert, type, error - ---- LuCI template library. -module "luci.template" - -config.template = config.template or {} -viewdir = config.template.viewdir or util.libpath() .. "/view" - - --- Define the namespace for template modules -context = util.threadlocal() - ---- Render a certain template. --- @param name Template name --- @param scope Scope to assign to template (optional) -function render(name, scope) - return Template(name):render(scope or getfenv(2)) -end - ---- Render a template from a string. --- @param template Template string --- @param scope Scope to assign to template (optional) -function render_string(template, scope) - return Template(nil, template):render(scope or getfenv(2)) -end - - --- Template class -Template = util.class() - --- Shared template cache to store templates in to avoid unnecessary reloading -Template.cache = setmetatable({}, {__mode = "v"}) - - --- Constructor - Reads and compiles the template on-demand -function Template.__init__(self, name, template) - if name then - self.template = self.cache[name] - self.name = name - else - self.name = "[string]" - end - - -- Create a new namespace for this template - self.viewns = context.viewns - - -- If we have a cached template, skip compiling and loading - if not self.template then - - -- Compile template - local err - local sourcefile - - if name then - sourcefile = viewdir .. "/" .. name .. ".htm" - self.template, _, err = tparser.parse(sourcefile) - else - sourcefile = "[string]" - self.template, _, err = tparser.parse_string(template) - end - - -- If we have no valid template throw error, otherwise cache the template - if not self.template then - error("Failed to load template '" .. self.name .. "'.\n" .. - "Error while parsing template '" .. sourcefile .. "':\n" .. - (err or "Unknown syntax error")) - elseif name then - self.cache[name] = self.template - end - end -end - - --- Renders a template -function Template.render(self, scope) - scope = scope or getfenv(2) - - -- Put our predefined objects in the scope of the template - setfenv(self.template, setmetatable({}, {__index = - function(tbl, key) - return rawget(tbl, key) or self.viewns[key] or scope[key] - end})) - - -- Now finally render the thing - local stat, err = util.copcall(self.template) - if not stat then - error("Failed to execute template '" .. self.name .. "'.\n" .. - "A runtime error occurred: " .. tostring(err or "(nil)")) - end -end diff --git a/modules/luci-base/luasrc/version.lua b/modules/luci-base/luasrc/version.lua deleted file mode 100644 index 8af2e80619..0000000000 --- a/modules/luci-base/luasrc/version.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Licensed to the public under the Apache License 2.0. - -module "luci.version" - -distname = "Host System" -distversion = "SDK" - -luciname = "LuCI" -luciversion = "SVN" diff --git a/modules/luci-base/luasrc/view/csrftoken.htm b/modules/luci-base/luasrc/view/csrftoken.htm deleted file mode 100644 index 57ac03f3bf..0000000000 --- a/modules/luci-base/luasrc/view/csrftoken.htm +++ /dev/null @@ -1,24 +0,0 @@ -<%# - Copyright 2015 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -

<%:Form token mismatch%>

-
- -

<%:The submitted security token is invalid or already expired!%>

- -

<%: - In order to prevent unauthorized access to the system, your request has - been blocked. Click "Continue »" below to return to the previous page. -%>

- -
- -

- Continue » -

- -<%+footer%> diff --git a/modules/luci-base/luasrc/view/empty_node_placeholder.htm b/modules/luci-base/luasrc/view/empty_node_placeholder.htm deleted file mode 100644 index b7e276b960..0000000000 --- a/modules/luci-base/luasrc/view/empty_node_placeholder.htm +++ /dev/null @@ -1,11 +0,0 @@ -<%# - Copyright 2010 Jo-Philipp Wich - Copyright 2018 Daniel F. Dickinson - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -

Component not present.

- -<%+footer%> diff --git a/modules/luci-base/luasrc/view/error404.htm b/modules/luci-base/luasrc/view/error404.htm deleted file mode 100644 index ff151d1834..0000000000 --- a/modules/luci-base/luasrc/view/error404.htm +++ /dev/null @@ -1,12 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> -

404 <%:Not Found%>

-

<%:Sorry, the object you requested was not found.%>

-

<%=message%>

-<%:Unable to dispatch%>: <%=url(unpack(luci.dispatcher.context.request))%> -<%+footer%> diff --git a/modules/luci-base/luasrc/view/error500.htm b/modules/luci-base/luasrc/view/error500.htm deleted file mode 100644 index 34a52cda84..0000000000 --- a/modules/luci-base/luasrc/view/error500.htm +++ /dev/null @@ -1,11 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> -

500 <%:Internal Server Error%>

-

<%:Sorry, the server encountered an unexpected error.%>

-
<%=message%>
-<%+footer%> diff --git a/modules/luci-base/luasrc/view/footer.htm b/modules/luci-base/luasrc/view/footer.htm deleted file mode 100644 index ba14ec8678..0000000000 --- a/modules/luci-base/luasrc/view/footer.htm +++ /dev/null @@ -1,27 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008-2019 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<% - local is_rollback_pending, rollback_time_remaining, rollback_session, rollback_token = luci.model.uci:rollback_pending() - - if is_rollback_pending or trigger_apply or trigger_revert then -%> - -<% - end - - include("themes/" .. theme .. "/footer") -%> diff --git a/modules/luci-base/luasrc/view/header.htm b/modules/luci-base/luasrc/view/header.htm deleted file mode 100644 index cffe9482ca..0000000000 --- a/modules/luci-base/luasrc/view/header.htm +++ /dev/null @@ -1,38 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008-2019 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<% - if not luci.dispatcher.context.template_header_sent then - include("themes/" .. theme .. "/header") - luci.dispatcher.context.template_header_sent = true - end - - local applyconf = luci.config and luci.config.apply -%> - - - - diff --git a/modules/luci-base/luasrc/view/indexer.htm b/modules/luci-base/luasrc/view/indexer.htm deleted file mode 100644 index 28fc3debc3..0000000000 --- a/modules/luci-base/luasrc/view/indexer.htm +++ /dev/null @@ -1,7 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<% include("themes/" .. theme .. "/indexer") %> \ No newline at end of file diff --git a/modules/luci-base/luasrc/view/sysauth.htm b/modules/luci-base/luasrc/view/sysauth.htm deleted file mode 100644 index 797c87a72e..0000000000 --- a/modules/luci-base/luasrc/view/sysauth.htm +++ /dev/null @@ -1,75 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008-2012 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -
- <%- if fuser then %> -
-

<%:Invalid username and/or password! Please try again.%>

-
- <% end -%> - -
-

<%:Authorization Required%>

-
- <%:Please enter your username and password.%> -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
- - -
-
- - -<% -local uci = require "luci.model.uci".cursor() -local fs = require "nixio.fs" -local https_key = uci:get("uhttpd", "main", "key") -local https_port = uci:get("uhttpd", "main", "listen_https") -if type(https_port) == "table" then - https_port = https_port[1] -end - -if https_port and fs.access(https_key) then - https_port = https_port:match("(%d+)$") -%> - - - -<% end %> - -<%+footer%> diff --git a/modules/luci-base/luasrc/view/view.htm b/modules/luci-base/luasrc/view/view.htm deleted file mode 100644 index b451e8cfbf..0000000000 --- a/modules/luci-base/luasrc/view/view.htm +++ /dev/null @@ -1,12 +0,0 @@ -<%+header%> - -
-
<%:Loading view…%>
- -
- -<%+footer%> diff --git a/modules/luci-base/luasrc/xml.lua b/modules/luci-base/luasrc/xml.lua deleted file mode 100644 index 30b37210bd..0000000000 --- a/modules/luci-base/luasrc/xml.lua +++ /dev/null @@ -1,26 +0,0 @@ --- Copyright 2008 Steven Barth --- Licensed to the public under the Apache License 2.0. - -local tparser = require "luci.template.parser" -local string = require "string" - -local tostring = tostring - -module "luci.xml" - --- --- String and data manipulation routines --- - -function pcdata(value) - return value and tparser.pcdata(tostring(value)) -end - -function striptags(value) - return value and tparser.striptags(tostring(value)) -end - - --- also register functions above in the central string class for convenience -string.pcdata = pcdata -string.striptags = striptags diff --git a/modules/luci-base/luasrc/xml.luadoc b/modules/luci-base/luasrc/xml.luadoc deleted file mode 100644 index 58de533966..0000000000 --- a/modules/luci-base/luasrc/xml.luadoc +++ /dev/null @@ -1,23 +0,0 @@ ----[[ -LuCI utility functions. -]] -module "luci.xml" - ----[[ -Create valid XML PCDATA from given string. - -@class function -@name pcdata -@param value String value containing the data to escape -@return String value containing the escaped data -]] - ----[[ -Strip HTML tags from given string. - -@class function -@name striptags -@param value String containing the HTML text -@return String with HTML tags stripped of -]] - diff --git a/modules/luci-base/root/usr/libexec/rpcd/luci b/modules/luci-base/root/usr/libexec/rpcd/luci deleted file mode 100755 index f124512f59..0000000000 --- a/modules/luci-base/root/usr/libexec/rpcd/luci +++ /dev/null @@ -1,682 +0,0 @@ -#!/usr/bin/env lua - -local json = require "luci.jsonc" -local fs = require "nixio.fs" - -local function readfile(path) - local s = fs.readfile(path) - return s and (s:gsub("^%s+", ""):gsub("%s+$", "")) -end - -local methods = { - getInitList = { - args = { name = "name" }, - call = function(args) - local sys = require "luci.sys" - local _, name, scripts = nil, nil, {} - for _, name in ipairs(args.name and { args.name } or sys.init.names()) do - local index = sys.init.index(name) - if index then - scripts[name] = { index = index, enabled = sys.init.enabled(name) } - else - return { error = "No such init script" } - end - end - return scripts - end - }, - - setInitAction = { - args = { name = "name", action = "action" }, - call = function(args) - local sys = require "luci.sys" - if type(sys.init[args.action]) ~= "function" then - return { error = "Invalid action" } - end - return { result = sys.init[args.action](args.name) } - end - }, - - getLocaltime = { - call = function(args) - return { result = os.time() } - end - }, - - setLocaltime = { - args = { localtime = 0 }, - call = function(args) - local sys = require "luci.sys" - local date = os.date("*t", args.localtime) - if date then - sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d' >/dev/null" %{ date.year, date.month, date.day, date.hour, date.min, date.sec }) - sys.call("/etc/init.d/sysfixtime restart >/dev/null") - end - return { result = args.localtime } - end - }, - - getTimezones = { - call = function(args) - local util = require "luci.util" - local zones = require "luci.sys.zoneinfo" - - local tz = readfile("/etc/TZ") - local res = util.ubus("uci", "get", { - config = "system", - section = "@system[0]", - option = "zonename" - }) - - local result = {} - local _, zone - for _, zone in ipairs(zones.TZ) do - result[zone[1]] = { - tzstring = zone[2], - active = (res and res.value == zone[1]) and true or nil - } - end - return result - end - }, - - getLEDs = { - call = function() - local iter = fs.dir("/sys/class/leds") - local result = { } - - if iter then - local led - for led in iter do - local m, s - - result[led] = { triggers = {} } - - s = readfile("/sys/class/leds/"..led.."/trigger") - for s in (s or ""):gmatch("%S+") do - m = s:match("^%[(.+)%]$") - result[led].triggers[#result[led].triggers+1] = m or s - result[led].active_trigger = m or result[led].active_trigger - end - - s = readfile("/sys/class/leds/"..led.."/brightness") - if s then - result[led].brightness = tonumber(s) - end - - s = readfile("/sys/class/leds/"..led.."/max_brightness") - if s then - result[led].max_brightness = tonumber(s) - end - end - end - - return result - end - }, - - getUSBDevices = { - call = function() - local fs = require "nixio.fs" - local iter = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer") - local result = { } - - if iter then - result.devices = {} - - local p - for p in iter do - local id = p:match("/([^/]+)/manufacturer$") - - result.devices[#result.devices+1] = { - id = id, - vid = readfile("/sys/bus/usb/devices/"..id.."/idVendor"), - pid = readfile("/sys/bus/usb/devices/"..id.."/idProduct"), - vendor = readfile("/sys/bus/usb/devices/"..id.."/manufacturer"), - product = readfile("/sys/bus/usb/devices/"..id.."/product"), - speed = tonumber((readfile("/sys/bus/usb/devices/"..id.."/product"))) - } - end - end - - iter = fs.glob("/sys/bus/usb/devices/*/*-port[0-9]*") - - if iter then - result.ports = {} - - local p - for p in iter do - local port = p:match("([^/]+)$") - local link = fs.readlink(p.."/device") - - result.ports[#result.ports+1] = { - port = port, - device = link and fs.basename(link) - } - end - end - - return result - end - }, - - getConntrackHelpers = { - call = function() - local ok, fd = pcall(io.open, "/usr/share/fw3/helpers.conf", "r") - local rv = {} - - if not (ok and fd) then - ok, fd = pcall(io.open, "/usr/share/firewall4/helpers", "r") - end - - if ok and fd then - local entry - - while true do - local line = fd:read("*l") - if not line then - break - end - - if line:match("^%s*config%s") then - if entry then - rv[#rv+1] = entry - end - entry = {} - else - local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$") - if opt and val then - opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1") - val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1") - entry[opt] = val - end - end - end - - if entry then - rv[#rv+1] = entry - end - - fd:close() - end - - return { result = rv } - end - }, - - getFeatures = { - call = function() - local fs = require "nixio.fs" - local rv = {} - local ok, fd - - rv.firewall = fs.access("/sbin/fw3") - rv.firewall4 = fs.access("/sbin/fw4") - rv.opkg = fs.access("/bin/opkg") - rv.offloading = fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt") or fs.access("/sys/module/nft_flow_offload/refcnt") - rv.br2684ctl = fs.access("/usr/sbin/br2684ctl") - rv.swconfig = fs.access("/sbin/swconfig") - rv.odhcpd = fs.access("/usr/sbin/odhcpd") - rv.zram = fs.access("/sys/class/zram-control") - rv.sysntpd = fs.readlink("/usr/sbin/ntpd") and true - rv.ipv6 = fs.access("/proc/net/ipv6_route") - rv.dropbear = fs.access("/usr/sbin/dropbear") - rv.cabundle = fs.access("/etc/ssl/certs/ca-certificates.crt") - rv.relayd = fs.access("/usr/sbin/relayd") - - local wifi_features = { "eap", "11n", "11ac", "11r", "acs", "sae", "owe", "suiteb192", "wep", "wps" } - - if fs.access("/usr/sbin/hostapd") then - rv.hostapd = { cli = fs.access("/usr/sbin/hostapd_cli") } - - local _, feature - for _, feature in ipairs(wifi_features) do - rv.hostapd[feature] = - (os.execute(string.format("/usr/sbin/hostapd -v%s >/dev/null 2>/dev/null", feature)) == 0) - end - end - - if fs.access("/usr/sbin/wpa_supplicant") then - rv.wpasupplicant = { cli = fs.access("/usr/sbin/wpa_cli") } - - local _, feature - for _, feature in ipairs(wifi_features) do - rv.wpasupplicant[feature] = - (os.execute(string.format("/usr/sbin/wpa_supplicant -v%s >/dev/null 2>/dev/null", feature)) == 0) - end - end - - ok, fd = pcall(io.popen, "dnsmasq --version 2>/dev/null") - if ok then - rv.dnsmasq = {} - - while true do - local line = fd:read("*l") - if not line then - break - end - - local opts = line:match("^Compile time options: (.+)$") - if opts then - local opt - for opt in opts:gmatch("%S+") do - local no = opt:match("^no%-(%S+)$") - rv.dnsmasq[string.lower(no or opt)] = not no - end - break - end - end - - fd:close() - end - - ok, fd = pcall(io.popen, "ipset --help 2>/dev/null") - if ok then - rv.ipset = {} - - local sets = false - - while true do - local line = fd:read("*l") - if not line then - break - elseif line:match("^Supported set types:") then - sets = true - elseif sets then - local set, ver = line:match("^%s+(%S+)%s+(%d+)") - if set and not rv.ipset[set] then - rv.ipset[set] = tonumber(ver) - end - end - end - - fd:close() - end - - return rv - end - }, - - getSwconfigFeatures = { - args = { switch = "switch0" }, - call = function(args) - local util = require "luci.util" - - -- Parse some common switch properties from swconfig help output. - local swc, err = io.popen("swconfig dev %s help 2>/dev/null" % util.shellquote(args.switch)) - if swc then - local is_port_attr = false - local is_vlan_attr = false - local rv = {} - - while true do - local line = swc:read("*l") - if not line then break end - - if line:match("^%s+%-%-vlan") then - is_vlan_attr = true - - elseif line:match("^%s+%-%-port") then - is_vlan_attr = false - is_port_attr = true - - elseif line:match("cpu @") then - rv.switch_title = line:match("^switch%d: %w+%((.-)%)") - rv.num_vlans = tonumber(line:match("vlans: (%d+)")) or 16 - rv.min_vid = 1 - - elseif line:match(": pvid") or line:match(": tag") or line:match(": vid") then - if is_vlan_attr then rv.vid_option = line:match(": (%w+)") end - - elseif line:match(": enable_vlan4k") then - rv.vlan4k_option = "enable_vlan4k" - - elseif line:match(": enable_vlan") then - rv.vlan_option = "enable_vlan" - - elseif line:match(": enable_learning") then - rv.learning_option = "enable_learning" - - elseif line:match(": enable_mirror_rx") then - rv.mirror_option = "enable_mirror_rx" - - elseif line:match(": max_length") then - rv.jumbo_option = "max_length" - end - end - - swc:close() - - if not next(rv) then - return { error = "No such switch" } - end - - return rv - else - return { error = err } - end - end - }, - - getSwconfigPortState = { - args = { switch = "switch0" }, - call = function(args) - local util = require "luci.util" - - local swc, err = io.popen("swconfig dev %s show 2>/dev/null" % util.shellquote(args.switch)) - if swc then - local ports = { } - - while true do - local line = swc:read("*l") - if not line or (line:match("^VLAN %d+:") and #ports > 0) then - break - end - - local pnum = line:match("^Port (%d+):$") - if pnum then - port = { - port = tonumber(pnum), - duplex = false, - speed = 0, - link = false, - auto = false, - rxflow = false, - txflow = false - } - - ports[#ports+1] = port - end - - if port then - local m - - if line:match("full[%- ]duplex") then - port.duplex = true - end - - m = line:match(" speed:(%d+)") - if m then - port.speed = tonumber(m) - end - - m = line:match("(%d+) Mbps") - if m and port.speed == 0 then - port.speed = tonumber(m) - end - - m = line:match("link: (%d+)") - if m and port.speed == 0 then - port.speed = tonumber(m) - end - - if line:match("link: ?up") or line:match("status: ?up") then - port.link = true - end - - if line:match("auto%-negotiate") or line:match("link:.-auto") then - port.auto = true - end - - if line:match("link:.-rxflow") then - port.rxflow = true - end - - if line:match("link:.-txflow") then - port.txflow = true - end - end - end - - swc:close() - - if not next(ports) then - return { error = "No such switch" } - end - - return { result = ports } - else - return { error = err } - end - end - }, - - setPassword = { - args = { username = "root", password = "password" }, - call = function(args) - local util = require "luci.util" - return { - result = (os.execute("(echo %s; sleep 1; echo %s) | /bin/busybox passwd %s >/dev/null 2>&1" %{ - luci.util.shellquote(args.password), - luci.util.shellquote(args.password), - luci.util.shellquote(args.username) - }) == 0) - } - end - }, - - getBlockDevices = { - call = function() - local fs = require "nixio.fs" - - local block = io.popen("/sbin/block info", "r") - if block then - local rv = {} - - while true do - local ln = block:read("*l") - if not ln then - break - end - - local dev = ln:match("^/dev/(.-):") - if dev then - local s = tonumber((fs.readfile("/sys/class/block/" .. dev .."/size"))) - local e = { - dev = "/dev/" .. dev, - size = s and s * 512 - } - - local key, val = { } - for key, val in ln:gmatch([[(%w+)="(.-)"]]) do - e[key:lower()] = val - end - - rv[dev] = e - end - end - - block:close() - - return rv - else - return { error = "Unable to execute block utility" } - end - end - }, - - setBlockDetect = { - call = function() - return { result = (os.execute("/sbin/block detect > /etc/config/fstab") == 0) } - end - }, - - getMountPoints = { - call = function() - local fs = require "nixio.fs" - - local fd, err = io.open("/proc/mounts", "r") - if fd then - local rv = {} - - while true do - local ln = fd:read("*l") - if not ln then - break - end - - local device, mount, fstype, options, freq, pass = ln:match("^(%S*) (%S*) (%S*) (%S*) (%d+) (%d+)$") - if device and mount then - device = device:gsub("\\(%d+)", function(n) return string.char(tonumber(n, 8)) end) - mount = mount:gsub("\\(%d+)", function(n) return string.char(tonumber(n, 8)) end) - - local stat = fs.statvfs(mount) - if stat and stat.blocks > 0 then - rv[#rv+1] = { - device = device, - mount = mount, - size = stat.bsize * stat.blocks, - avail = stat.bsize * stat.bavail, - free = stat.bsize * stat.bfree - } - end - end - end - - fd:close() - - return { result = rv } - else - return { error = err } - end - end - }, - - getRealtimeStats = { - args = { mode = "interface", device = "eth0" }, - call = function(args) - local util = require "luci.util" - - local flags - if args.mode == "interface" then - flags = "-i %s" % util.shellquote(args.device) - elseif args.mode == "wireless" then - flags = "-r %s" % util.shellquote(args.device) - elseif args.mode == "conntrack" then - flags = "-c" - elseif args.mode == "load" then - flags = "-l" - else - return { error = "Invalid mode" } - end - - local fd, err = io.popen("luci-bwc %s" % flags, "r") - if fd then - local parse = json.new() - local done - - parse:parse("[") - - while true do - local ln = fd:read("*l") - if not ln then - break - end - - done, err = parse:parse((ln:gsub("%d+", "%1.0"))) - - if done then - err = "Unexpected JSON data" - end - - if err then - break - end - end - - fd:close() - - done, err = parse:parse("]") - - if err then - return { error = err } - elseif not done then - return { error = "Incomplete JSON data" } - else - return { result = parse:get() } - end - else - return { error = err } - end - end - }, - - getConntrackList = { - call = function() - local sys = require "luci.sys" - return { result = sys.net.conntrack() } - end - }, - - getProcessList = { - call = function() - local sys = require "luci.sys" - local res = {} - for _, v in pairs(sys.process.list()) do - res[#res + 1] = v - end - return { result = res } - end - } -} - -local function parseInput() - local parse = json.new() - local done, err - - while true do - local chunk = io.read(4096) - if not chunk then - break - elseif not done and not err then - done, err = parse:parse(chunk) - end - end - - if not done then - print(json.stringify({ error = err or "Incomplete input" })) - os.exit(1) - end - - return parse:get() -end - -local function validateArgs(func, uargs) - local method = methods[func] - if not method then - print(json.stringify({ error = "Method not found" })) - os.exit(1) - end - - if type(uargs) ~= "table" then - print(json.stringify({ error = "Invalid arguments" })) - os.exit(1) - end - - uargs.ubus_rpc_session = nil - - local k, v - local margs = method.args or {} - for k, v in pairs(uargs) do - if margs[k] == nil or - (v ~= nil and type(v) ~= type(margs[k])) - then - print(json.stringify({ error = "Invalid arguments" })) - os.exit(1) - end - end - - return method -end - -if arg[1] == "list" then - local _, method, rv = nil, nil, {} - for _, method in pairs(methods) do rv[_] = method.args or {} end - print((json.stringify(rv):gsub(":%[%]", ":{}"))) -elseif arg[1] == "call" then - local args = parseInput() - local method = validateArgs(arg[2], args) - local result, code = method.call(args) - print((json.stringify(result):gsub("^%[%]$", "{}"))) - os.exit(code or 0) -end diff --git a/modules/luci-base/root/usr/share/luci/menu.d/luci-base.json b/modules/luci-base/root/usr/share/luci/menu.d/luci-base.json index 605c7ab777..000c368151 100644 --- a/modules/luci-base/root/usr/share/luci/menu.d/luci-base.json +++ b/modules/luci-base/root/usr/share/luci/menu.d/luci-base.json @@ -61,7 +61,7 @@ "admin/translations/*": { "action": { - "type": "call", + "type": "function", "module": "luci.controller.admin.index", "function": "action_translations" }, @@ -70,7 +70,7 @@ "admin/ubus/*": { "action": { - "type": "call", + "type": "function", "module": "luci.controller.admin.index", "function": "action_ubus" }, @@ -81,7 +81,7 @@ "title": "Logout", "order": 999, "action": { - "type": "call", + "type": "function", "module": "luci.controller.admin.index", "function": "action_logout" }, @@ -99,7 +99,7 @@ "admin/uci/revert": { "action": { - "type": "call", + "type": "function", "module": "luci.controller.admin.uci", "function": "action_revert", "post": true @@ -109,7 +109,7 @@ "admin/uci/apply_rollback": { "cors": true, "action": { - "type": "call", + "type": "function", "module": "luci.controller.admin.uci", "function": "action_apply_rollback", "post": true @@ -122,7 +122,7 @@ "admin/uci/apply_unchecked": { "cors": true, "action": { - "type": "call", + "type": "function", "module": "luci.controller.admin.uci", "function": "action_apply_unchecked", "post": true @@ -135,7 +135,7 @@ "admin/uci/confirm": { "cors": true, "action": { - "type": "call", + "type": "function", "module": "luci.controller.admin.uci", "function": "action_confirm" }, @@ -144,7 +144,7 @@ "admin/menu": { "action": { - "type": "call", + "type": "function", "module": "luci.controller.admin.index", "function": "action_menu" }, diff --git a/modules/luci-base/root/usr/share/rpcd/ucode/luci b/modules/luci-base/root/usr/share/rpcd/ucode/luci new file mode 100644 index 0000000000..794676abc6 --- /dev/null +++ b/modules/luci-base/root/usr/share/rpcd/ucode/luci @@ -0,0 +1,512 @@ +// Copyright 2022 Jo-Philipp Wich +// Licensed to the public under the Apache License 2.0. + +'use strict'; + +import { stdin, access, dirname, basename, open, popen, glob, lsdir, readfile, readlink, error } from 'fs'; +import { cursor } from 'uci'; + +import { init_list, init_index, init_enabled, init_action, conntrack_list, process_list } from 'luci.sys'; +import { statvfs } from 'luci.core'; + +import timezones from 'luci.zoneinfo'; + + +function shellquote(s) { + return `'${replace(s, "'", "'\\''")}'`; +} + +const methods = { + getInitList: { + args: { name: 'name' }, + call: function(request) { + let scripts = {}; + + for (let name in filter(init_list(), i => !request.args.name || i == request.args.name)) { + let idx = init_index(name); + + scripts[name] = { + index: idx[0], + stop: idx[1], + enabled: init_enabled(name) + }; + } + + return length(scripts) ? scripts : { error: 'No such init script' }; + } + }, + + setInitAction: { + args: { name: 'name', action: 'action' }, + call: function(request) { + switch (request.args.action) { + case 'enable': + case 'disable': + case 'start': + case 'stop': + case 'restart': + case 'reload': + const rc = init_action(request.args.name, request.args.action); + + if (rc === false) + return { error: 'No such init script' }; + + return { result: rc == 0 }; + + default: + return { error: 'Invalid action' }; + } + } + }, + + getLocaltime: { + call: function(request) { + return { result: time() }; + } + }, + + setLocaltime: { + args: { localtime: 0 }, + call: function(request) { + let t = localtime(request.args.localtime); + + if (t) { + system(sprintf('date -s "%04d-%02d-%02d %02d:%02d:%02d" >/dev/null', t.year, t.mon, t.mday, t.hour, t.min, t.sec)); + system('/etc/init.d/sysfixtime restart >/dev/null'); + } + + return { result: request.args.localtime }; + } + }, + + getTimezones: { + call: function(request) { + let tz = trim(readfile('/etc/TZ')); + let zn = cursor()?.get?.('system', '@system[0]', 'zonename'); + let result = {}; + + for (let zone, tzstring in timezones) { + result[zone] = { tzstring }; + + if (zn == zone) + result[zone].active = true; + }; + + return result; + } + }, + + getLEDs: { + call: function() { + let result = {}; + + for (let led in lsdir('/sys/class/leds')) { + let s; + + result[led] = { triggers: [] }; + + s = trim(readfile(`/sys/class/leds/${led}/trigger`)); + for (let trigger in split(s, ' ')) { + push(result[led].triggers, trim(trigger, '[]')); + + if (trigger != result[led].triggers[-1]) + result[led].active_trigger = result[led].triggers[-1]; + } + + s = readfile(`/sys/class/leds/${led}/brightness`); + result[led].brightness = +s; + + s = readfile(`/sys/class/leds/${led}/max_brightness`); + result[led].max_brightness = +s; + } + + return result; + } + }, + + getUSBDevices: { + call: function() { + let result = { devices: [], ports: [] }; + + for (let path in glob('/sys/bus/usb/devices/[0-9]*/manufacturer')) { + let id = basename(dirname(path)); + + push(result.devices, { + id, + vid: trim(readfile(`/sys/bus/usb/devices/${id}/idVendor`)), + pid: trim(readfile(`/sys/bus/usb/devices/${id}/idProduct`)), + vendor: trim(readfile(path)), + product: trim(readfile(`/sys/bus/usb/devices/${id}/product`)), + speed: +readfile(`/sys/bus/usb/devices/${id}/speed`) + }); + } + + for (let path in glob('/sys/bus/usb/devices/*/*-port[0-9]*')) { + let port = basename(path); + let link = readlink(`${path}/device`); + + push(result.ports, { + port, + device: basename(link) + }); + } + + return result; + } + }, + + getConntrackHelpers: { + call: function() { + const uci = cursor(); + let helpers = []; + + uci.load('/usr/share/firewall4/helpers'); + uci.load('/usr/share/fw3/helpers.conf'); + + uci.foreach('helpers', 'helper', (s) => { + push(helpers, { + name: s.name, + description: s.description, + module: s.module, + family: s.family, + proto: s.proto, + port: s.port + }); + }); + + return { result: helpers }; + } + }, + + getFeatures: { + call: function() { + let result = { + firewall: access('/sbin/fw3') == true, + firewall4: access('/sbin/fw4') == true, + opkg: access('/bin/opkg') == true, + offloading: access('/sys/module/xt_FLOWOFFLOAD/refcnt') == true || access('/sys/module/nft_flow_offload/refcnt') == true, + br2684ctl: access('/usr/sbin/br2684ctl') == true, + swconfig: access('/sbin/swconfig') == true, + odhcpd: access('/usr/sbin/odhcpd') == true, + zram: access('/sys/class/zram-control') == true, + sysntpd: readlink('/usr/sbin/ntpd') != null, + ipv6: access('/proc/net/ipv6_route') == true, + dropbear: access('/usr/sbin/dropbear') == true, + cabundle: access('/etc/ssl/certs/ca-certificates.crt') == true, + relayd: access('/usr/sbin/relayd') == true, + }; + + const wifi_features = [ 'eap', '11n', '11ac', '11r', 'acs', 'sae', 'owe', 'suiteb192', 'wep', 'wps' ]; + + if (access('/usr/sbin/hostapd')) { + result.hostapd = { cli: access('/usr/sbin/hostapd_cli') == true }; + + for (let feature in wifi_features) + result.hostapd[feature] = system(`/usr/sbin/hostapd -v${feature} >/dev/null 2>/dev/null`) == 0; + } + + if (access('/usr/sbin/wpa_supplicant')) { + result.wpasupplicant = { cli: access('/usr/sbin/wpa_cli') == true }; + + for (let feature in wifi_features) + result.wpasupplicant[feature] = system(`/usr/sbin/wpa_supplicant -v${feature} >/dev/null 2>/dev/null`) == 0; + } + + let fd = popen('dnsmasq --version 2>/dev/null'); + + if (fd) { + const m = match(fd.read('all'), /^Compile time options: (.+)$/s); + + for (let opt in split(m?.[1], ' ')) { + let f = replace(opt, 'no-', '', 1); + + result.dnsmasq ??= {}; + result.dnsmasq[lc(f)] = (f == opt); + } + + fd.close(); + } + + fd = popen('ipset --help 2>/dev/null'); + + if (fd) { + for (let line = fd.read('line'), flag = false; length(line); line = fd.read('line')) { + if (line == 'Supported set types:\n') { + flag = true; + } + else if (flag) { + const m = match(line, /^ +([\w:,]+)\t+([0-9]+)\t/); + + if (m) { + result.ipset ??= {}; + result.ipset[m[1]] ??= +m[2]; + } + } + } + + fd.close(); + } + + return result; + } + }, + + getSwconfigFeatures: { + args: { switch: 'switch0' }, + call: function(request) { + // Parse some common switch properties from swconfig help output. + const swc = popen(`swconfig dev ${shellquote(request.args.switch)} help 2>/dev/null`); + + if (swc) { + let is_port_attr = false; + let is_vlan_attr = false; + let result = {}; + + for (let line = swc.read('line'); length(line); line = swc.read('line')) { + if (match(line, /^\s+--vlan/)) { + is_vlan_attr = true; + } + else if (match(line, /^\s+--port/)) { + is_vlan_attr = false; + is_port_attr = true; + } + else if (match(line, /cpu @/)) { + result.switch_title = match(line, /^switch[0-9]+: \w+\((.+)\)/)?.[1]; + result.num_vlans = match(line, /vlans: ([0-9]+)/)?.[1] ?? 16; + result.min_vid = 1; + } + else if (match(line, /: (pvid|tag|vid)/)) { + if (is_vlan_attr) + result.vid_option = match(line, /: (\w+)/)?.[1]; + } + else if (match(line, /: enable_vlan4k/)) { + result.vlan4k_option = 'enable_vlan4k'; + } + else if (match(line, /: enable_vlan/)) { + result.vlan_option = 'enable_vlan'; + } + else if (match(line, /: enable_learning/)) { + result.learning_option = 'enable_learning'; + } + else if (match(line, /: enable_mirror_rx/)) { + result.mirror_option = 'enable_mirror_rx'; + } + else if (match(line, /: max_length/)) { + result.jumbo_option = 'max_length'; + } + } + + swc.close(); + + if (!length(result)) + return { error: 'No such switch' }; + + return result; + } + else { + return { error: error() }; + } + } + }, + + getSwconfigPortState: { + args: { switch: 'switch0' }, + call: function(request) { + const swc = popen(`swconfig dev ${shellquote(request.args.switch)} show 2>/dev/null`); + + if (swc) { + let ports = [], port; + + for (let line = swc.read('line'); length(line); line = swc.read('line')) { + if (match(line, /^VLAN [0-9]+:/) && length(ports)) + break; + + let pnum = match(line, /^Port ([0-9]+):/)?.[1]; + + if (pnum) { + port = { + port: +pnum, + duplex: false, + speed: 0, + link: false, + auto: false, + rxflow: false, + txflow: false + }; + + push(ports, port); + } + + if (port) { + let m; + + if (match(line, /full[ -]duplex/)) + port.duplex = true; + + if ((m = match(line, / speed:([0-9]+)/)) != null) + port.speed = +m[1]; + + if ((m = match(line, /([0-9]+) Mbps/)) != null && !port.speed) + port.speed = +m[1]; + + if ((m = match(line, /link: ([0-9]+)/)) != null && !port.speed) + port.speed = +m[1]; + + if (match(line, /(link|status): ?up/)) + port.link = true; + + if (match(line, /auto-negotiate|link:.*auto/)) + port.auto = true; + + if (match(line, /link:.*rxflow/)) + port.rxflow = true; + + if (match(line, /link:.*txflow/)) + port.txflow = true; + } + } + + swc.close(); + + if (!length(ports)) + return { error: 'No such switch' }; + + return { result: ports }; + } + else { + return { error: error() }; + } + } + }, + + setPassword: { + args: { username: 'root', password: 'password' }, + call: function(request) { + const u = shellquote(request.args.username); + const p = shellquote(request.args.password); + + return { + result: system(`(echo ${p}; sleep 1; echo ${p}) | /bin/busybox passwd ${u} >/dev/null 2>&1`) == 0 + }; + } + }, + + getBlockDevices: { + call: function() { + const block = popen('/sbin/block info 2>/dev/null'); + + if (block) { + let result = {}; + + for (let line = block.read('line'); length(line); line = block.read('line')) { + let dev = match(line, /^\/dev\/([^:]+):/)?.[1]; + + if (dev) { + let e = result[dev] = { + dev: `/dev/${dev}`, + size: +readfile(`/sys/class/block/${dev}/size`) * 512 + }; + + for (m in match(line, / (\w+)="([^"]+)"/g)) + e[lc(m[1])] = m[2]; + } + } + + block.close(); + + return result; + } + else { + return { error: 'Unable to execute block utility' }; + } + } + }, + + setBlockDetect: { + call: function() { + return { result: system('/sbin/block detect > /etc/config/fstab') == 0 }; + } + }, + + getMountPoints: { + call: function() { + const fd = open('/proc/mounts', 'r'); + + if (fd) { + let result = []; + + for (let line = fd.read('line'); length(line); line = fd.read('line')) { + const m = split(line, ' '); + const device = replace(m[0], /\\([0-9][0-9][0-9])/g, (m, n) => char(int(n, 8))); + const mount = replace(m[1], /\\([0-9][0-9][0-9])/g, (m, n) => char(int(n, 8))); + const stat = statvfs(mount); + + if (stat?.blocks > 0) { + push(result, { + device, mount, + size: stat.bsize * stat.blocks, + avail: stat.bsize * stat.bavail, + free: stat.bsize * stat.bfree + }); + } + } + + fd.close(); + + return { result }; + } + else { + return { error: error() }; + } + } + }, + getRealtimeStats: { + args: { mode: 'interface', device: 'eth0' }, + call: function(request) { + let flags; + + if (request.args.mode == 'interface') + flags = `-i ${shellquote(request.args.device)}`; + else if (request.args.mode == 'wireless') + flags = `-r ${shellquote(request.args.device)}`; + else if (request.args.mode == 'conntrack') + flags = '-c'; + else if (request.args.mode == 'load') + flags = '-l'; + else + return { error: 'Invalid mode' }; + + const fd = popen(`luci-bwc ${flags}`, 'r'); + + if (fd) { + let result; + + try { + result = { result: json(`[${fd.read('all')}]`) }; + } + catch (err) { + result = { error: err }; + } + + return result; + } + else { + return { error: error() }; + } + } + }, + + getConntrackList: { + call: function() { + return { result: conntrack_list() }; + } + }, + + getProcessList: { + call: function() { + return { result: process_list() }; + } + } +}; + +return { luci: methods }; diff --git a/modules/luci-base/src/Makefile b/modules/luci-base/src/Makefile index 2a425d5ab7..896aeb0a38 100644 --- a/modules/luci-base/src/Makefile +++ b/modules/luci-base/src/Makefile @@ -4,29 +4,31 @@ contrib/lemon: contrib/lemon.c contrib/lempar.c cc -o contrib/lemon $< -plural_formula.c: plural_formula.y contrib/lemon +lib/plural_formula.c: lib/plural_formula.y contrib/lemon ./contrib/lemon -q $< -template_lmo.c: plural_formula.c +lib/lmo.c: lib/plural_formula.c + +core.so: lib/luci.o lib/lmo.o lib/plural_formula.o + $(CC) $(LDFLAGS) -shared -o $@ $^ + +version.uc: + echo "export const revision = '$(LUCI_VERSION)', branch = '$(LUCI_GITBRANCH)';" > $@ clean: - rm -f contrib/lemon po2lmo parser.so version.lua plural_formula.c plural_formula.h *.o + rm -f contrib/lemon lib/*.o lib/plural_formula.c lib/plural_formula.h core.so version.uc jsmin: jsmin.o $(CC) $(LDFLAGS) -o $@ $^ -po2lmo: po2lmo.o template_lmo.o plural_formula.o +po2lmo: po2lmo.o lib/lmo.o lib/plural_formula.o $(CC) $(LDFLAGS) -o $@ $^ -parser.so: template_parser.o template_utils.o template_lmo.o template_lualib.o plural_formula.o - $(CC) $(LDFLAGS) -shared -o $@ $^ - -version.lua: - ./mkversion.sh $@ $(LUCI_VERSION) "$(LUCI_GITBRANCH)" - -compile: parser.so version.lua +compile: core.so version.uc install: compile - mkdir -p $(DESTDIR)/usr/lib/lua/luci/template - cp parser.so $(DESTDIR)/usr/lib/lua/luci/template/parser.so - cp version.lua $(DESTDIR)/usr/lib/lua/luci/version.lua + mkdir -p $(DESTDIR)/usr/lib/ucode/luci + cp core.so $(DESTDIR)/usr/lib/ucode/luci/core.so + + mkdir -p $(DESTDIR)/usr/share/ucode/luci + cp version.uc $(DESTDIR)/usr/share/ucode/luci/version.uc diff --git a/modules/luci-base/src/lib/lmo.c b/modules/luci-base/src/lib/lmo.c new file mode 100644 index 0000000000..da521bc98b --- /dev/null +++ b/modules/luci-base/src/lib/lmo.c @@ -0,0 +1,636 @@ +/* + * lmo - Lua Machine Objects - Base functions + * + * Copyright (C) 2009-2010 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lmo.h" +#include "plural_formula.h" + +/* + * Hash function from http://www.azillionmonkeys.com/qed/hash.html + * Copyright (C) 2004-2008 by Paul Hsieh + */ + +uint32_t sfh_hash(const char *data, size_t len, uint32_t init) +{ + uint32_t hash = init, tmp; + int rem; + + if (len <= 0 || data == NULL) return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (;len > 0; len--) { + hash += sfh_get16(data); + tmp = (sfh_get16(data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2*sizeof(uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: hash += sfh_get16(data); + hash ^= hash << 16; + hash ^= (signed char)data[sizeof(uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: hash += sfh_get16(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += (signed char)*data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +uint32_t lmo_canon_hash(const char *str, int len, + const char *ctx, int ctxlen, int plural) +{ + char res[4096]; + char *ptr, *end, prev; + int off; + + if (!str) + return 0; + + ptr = res; + end = res + sizeof(res); + + if (ctx) + { + for (prev = ' ', off = 0; off < ctxlen; prev = *ctx, off++, ctx++) + { + if (ptr >= end) + return 0; + + if (isspace(*ctx)) + { + if (!isspace(prev)) + *ptr++ = ' '; + } + else + { + *ptr++ = *ctx; + } + } + + if ((ptr > res) && isspace(*(ptr-1))) + ptr--; + + if (ptr >= end) + return 0; + + *ptr++ = '\1'; + } + + for (prev = ' ', off = 0; off < len; prev = *str, off++, str++) + { + if (ptr >= end) + return 0; + + if (isspace(*str)) + { + if (!isspace(prev)) + *ptr++ = ' '; + } + else + { + *ptr++ = *str; + } + } + + if ((ptr > res) && isspace(*(ptr-1))) + ptr--; + + if (plural > -1) + { + if (plural >= 100 || ptr + 3 >= end) + return 0; + + ptr += snprintf(ptr, 3, "\2%d", plural); + } + + return sfh_hash(res, ptr - res, ptr - res); +} + +lmo_archive_t * lmo_open(const char *file) +{ + int in = -1; + uint32_t idx_offset = 0; + struct stat s; + + lmo_archive_t *ar = NULL; + + if (stat(file, &s) == -1) + goto err; + + if ((in = open(file, O_RDONLY)) == -1) + goto err; + + if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL) + { + memset(ar, 0, sizeof(*ar)); + + ar->fd = in; + ar->size = s.st_size; + + fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC); + + if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED) + goto err; + + idx_offset = ntohl(*((const uint32_t *) + (ar->mmap + ar->size - sizeof(uint32_t)))); + + if (idx_offset >= ar->size) + goto err; + + ar->index = (lmo_entry_t *)(ar->mmap + idx_offset); + ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t); + ar->end = ar->mmap + ar->size; + + return ar; + } + +err: + if (in > -1) + close(in); + + if (ar != NULL) + { + if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) + munmap(ar->mmap, ar->size); + + free(ar); + } + + return NULL; +} + +void lmo_close(lmo_archive_t *ar) +{ + if (ar != NULL) + { + if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) + munmap(ar->mmap, ar->size); + + close(ar->fd); + free(ar); + + ar = NULL; + } +} + + +lmo_catalog_t *_lmo_catalogs = NULL; +lmo_catalog_t *_lmo_active_catalog = NULL; + +int lmo_load_catalog(const char *lang, const char *dir) +{ + DIR *dh = NULL; + char pattern[16]; + char path[PATH_MAX]; + struct dirent *de = NULL; + + lmo_archive_t *ar = NULL; + lmo_catalog_t *cat = NULL; + + if (!lmo_change_catalog(lang)) + return 0; + + if (!dir || !(dh = opendir(dir))) + goto err; + + if (!(cat = malloc(sizeof(*cat)))) + goto err; + + memset(cat, 0, sizeof(*cat)); + + snprintf(cat->lang, sizeof(cat->lang), "%s", lang); + snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang); + + while ((de = readdir(dh)) != NULL) + { + if (!fnmatch(pattern, de->d_name, 0)) + { + snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); + ar = lmo_open(path); + + if (ar) + { + ar->next = cat->archives; + cat->archives = ar; + } + } + } + + closedir(dh); + + cat->next = _lmo_catalogs; + _lmo_catalogs = cat; + + if (!_lmo_active_catalog) + _lmo_active_catalog = cat; + + return cat->archives ? 0 : -1; + +err: + if (dh) closedir(dh); + if (cat) free(cat); + + return -1; +} + +int lmo_change_catalog(const char *lang) +{ + lmo_catalog_t *cat; + + for (cat = _lmo_catalogs; cat; cat = cat->next) + { + if (!strncmp(cat->lang, lang, sizeof(cat->lang))) + { + _lmo_active_catalog = cat; + return 0; + } + } + + return -1; +} + +static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash) +{ + unsigned int m, l, r; + uint32_t k; + + l = 0; + r = ar->length - 1; + + while (1) + { + m = l + ((r - l) / 2); + + if (r < l) + break; + + k = ntohl(ar->index[m].key_id); + + if (k == hash) + return &ar->index[m]; + + if (k > hash) + { + if (!m) + break; + + r = m - 1; + } + else + { + l = m + 1; + } + } + + return NULL; +} + +void *pluralParseAlloc(void *(*)(size_t)); +void pluralParse(void *, int, int, void *); +void pluralParseFree(void *, void (*)(void *)); + +static int lmo_eval_plural(const char *expr, int len, int val) +{ + struct { int num; int res; } s = { .num = val, .res = -1 }; + const char *p = NULL; + void *pParser = NULL; + int t, n; + char c; + + while (len > 7) { + if (*expr == 'p') { + if (!strncmp(expr, "plural=", 7)) { + p = expr + 7; + len -= 7; + break; + } + } + + expr++; + len--; + } + + if (!p) + goto out; + + pParser = pluralParseAlloc(malloc); + + if (!pParser) + goto out; + + while (len-- > 0) { + c = *p++; + t = -1; + n = 0; + + switch (c) { + case ' ': + case '\t': + continue; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + t = T_NUM; + n = c - '0'; + + while (*p >= '0' && *p <= '9') { + n *= 10; + n += *p - '0'; + p++; + } + + break; + + case '=': + if (*p == '=') { + t = T_EQ; + p++; + } + + break; + + case '!': + if (*p == '=') { + t = T_NE; + p++; + } + else { + t = T_NOT; + } + + break; + + case '&': + if (*p == '&') { + t = T_AND; + p++; + } + + break; + + case '|': + if (*p == '|') { + t = T_OR; + p++; + } + + break; + + case '<': + if (*p == '=') { + t = T_LE; + p++; + } + else { + t = T_LT; + } + + break; + + case '>': + if (*p == '=') { + t = T_GE; + p++; + } + else { + t = T_GT; + } + + break; + + case '*': + t = T_MUL; + break; + + case '/': + t = T_DIV; + break; + + case '%': + t = T_MOD; + break; + + case '+': + t = T_ADD; + break; + + case '-': + t = T_SUB; + break; + + case 'n': + t = T_N; + break; + + case '?': + t = T_QMARK; + break; + + case ':': + t = T_COLON; + break; + + case '(': + t = T_LPAREN; + break; + + case ')': + t = T_RPAREN; + break; + + case ';': + case '\n': + case '\0': + t = 0; + break; + } + + /* syntax error */ + if (t < 0) + goto out; + + pluralParse(pParser, t, n, &s); + + /* eof */ + if (t == 0) + break; + } + + pluralParse(pParser, 0, 0, &s); + +out: + pluralParseFree(pParser, free); + + return s.res; +} + +int lmo_translate(const char *key, int keylen, char **out, int *outlen) +{ + return lmo_translate_ctxt(key, keylen, NULL, 0, out, outlen); +} + +int lmo_translate_ctxt(const char *key, int keylen, + const char *ctx, int ctxlen, + char **out, int *outlen) +{ + uint32_t hash; + lmo_entry_t *e; + lmo_archive_t *ar; + + if (!key || !_lmo_active_catalog) + return -2; + + hash = lmo_canon_hash(key, keylen, ctx, ctxlen, -1); + + if (hash > 0) + { + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) + { + if ((e = lmo_find_entry(ar, hash)) != NULL) + { + *out = ar->mmap + ntohl(e->offset); + *outlen = ntohl(e->length); + return 0; + } + } + } + + return -1; +} + +int lmo_translate_plural(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + char **out, int *outlen) +{ + return lmo_translate_plural_ctxt(n, skey, skeylen, pkey, pkeylen, + NULL, 0, out, outlen); +} + +int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + const char *ctx, int ctxlen, + char **out, int *outlen) +{ + int pid = -1; + uint32_t hash; + lmo_entry_t *e; + lmo_archive_t *ar; + + if (!skey || !pkey || !_lmo_active_catalog) + return -2; + + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) { + e = lmo_find_entry(ar, 0); + + if (e != NULL) { + pid = lmo_eval_plural(ar->mmap + ntohl(e->offset), ntohl(e->length), n); + break; + } + } + + if (pid == -1) + pid = (n != 1); + + hash = lmo_canon_hash(skey, skeylen, ctx, ctxlen, pid); + + if (hash == 0) + return -1; + + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) + { + if ((e = lmo_find_entry(ar, hash)) != NULL) + { + *out = ar->mmap + ntohl(e->offset); + *outlen = ntohl(e->length); + return 0; + } + } + + if (n != 1) + { + *out = (char *)pkey; + *outlen = pkeylen; + } + else + { + *out = (char *)skey; + *outlen = skeylen; + } + + return 0; +} + +void lmo_iterate(lmo_iterate_cb_t cb, void *priv) +{ + unsigned int i; + lmo_entry_t *e; + lmo_archive_t *ar; + + if (!_lmo_active_catalog) + return; + + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) + for (i = 0, e = &ar->index[0]; i < ar->length; e = &ar->index[++i]) + cb(ntohl(e->key_id), ar->mmap + ntohl(e->offset), ntohl(e->length), priv); +} + +void lmo_close_catalog(const char *lang) +{ + lmo_archive_t *ar, *next; + lmo_catalog_t *cat, *prev; + + for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next) + { + if (!strncmp(cat->lang, lang, sizeof(cat->lang))) + { + if (prev) + prev->next = cat->next; + else + _lmo_catalogs = cat->next; + + for (ar = cat->archives; ar; ar = next) + { + next = ar->next; + lmo_close(ar); + } + + free(cat); + break; + } + } +} diff --git a/modules/luci-base/src/lib/lmo.h b/modules/luci-base/src/lib/lmo.h new file mode 100644 index 0000000000..744209f62c --- /dev/null +++ b/modules/luci-base/src/lib/lmo.h @@ -0,0 +1,108 @@ +/* + * lmo - Lua Machine Objects - General header + * + * Copyright (C) 2009-2012 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TEMPLATE_LMO_H_ +#define _TEMPLATE_LMO_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (defined(__GNUC__) && defined(__i386__)) +#define sfh_get16(d) (*((const uint16_t *) (d))) +#else +#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + +#ifndef __hidden +#define __hidden __attribute__((visibility("hidden"))) +#endif + + +struct lmo_entry { + uint32_t key_id; + uint32_t val_id; + uint32_t offset; + uint32_t length; +} __attribute__((packed)); + +typedef struct lmo_entry lmo_entry_t; + + +struct lmo_archive { + int fd; + int length; + uint32_t size; + lmo_entry_t *index; + char *mmap; + char *end; + struct lmo_archive *next; +}; + +typedef struct lmo_archive lmo_archive_t; + + +struct lmo_catalog { + char lang[6]; + struct lmo_archive *archives; + struct lmo_catalog *next; +}; + +typedef struct lmo_catalog lmo_catalog_t; + +typedef void (*lmo_iterate_cb_t)(uint32_t, const char *, int, void *); + +__hidden uint32_t sfh_hash(const char *data, size_t len, uint32_t init); +__hidden uint32_t lmo_canon_hash(const char *data, int len, + const char *ctx, int ctxlen, int plural); + +__hidden lmo_archive_t * lmo_open(const char *file); +__hidden void lmo_close(lmo_archive_t *ar); + + +__hidden extern lmo_catalog_t *_lmo_catalogs; +__hidden extern lmo_catalog_t *_lmo_active_catalog; + +__hidden int lmo_load_catalog(const char *lang, const char *dir); +__hidden int lmo_change_catalog(const char *lang); +__hidden int lmo_translate(const char *key, int keylen, char **out, int *outlen); +__hidden int lmo_translate_ctxt(const char *key, int keylen, + const char *ctx, int ctxlen, char **out, int *outlen); +__hidden int lmo_translate_plural(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + char **out, int *outlen); +__hidden int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + const char *ctx, int ctxlen, + char **out, int *outlen); +__hidden void lmo_iterate(lmo_iterate_cb_t cb, void *priv); +__hidden void lmo_close_catalog(const char *lang); + +#endif diff --git a/modules/luci-base/src/lib/luci.c b/modules/luci-base/src/lib/luci.c new file mode 100644 index 0000000000..e6860e727d --- /dev/null +++ b/modules/luci-base/src/lib/luci.c @@ -0,0 +1,383 @@ +/* + * LuCI low level routines - ucode binding + * + * Copyright (C) 2009-2022 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lmo.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/* translation catalog functions */ + +static uc_value_t * +uc_luci_load_catalog(uc_vm_t *vm, size_t nargs) { + uc_value_t *lang = uc_fn_arg(0); + uc_value_t *dir = uc_fn_arg(1); + + if (lang && ucv_type(lang) != UC_STRING) + return NULL; + + if (dir && ucv_type(dir) != UC_STRING) + return NULL; + + return ucv_boolean_new(lmo_load_catalog( + lang ? ucv_string_get(lang) : "en", + ucv_string_get(dir)) == 0); +} + +static uc_value_t * +uc_luci_close_catalog(uc_vm_t *vm, size_t nargs) { + uc_value_t *lang = uc_fn_arg(0); + + if (lang && ucv_type(lang) != UC_STRING) + return NULL; + + lmo_close_catalog(lang ? ucv_string_get(lang) : "en"); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_luci_change_catalog(uc_vm_t *vm, size_t nargs) { + uc_value_t *lang = uc_fn_arg(0); + + if (lang && ucv_type(lang) != UC_STRING) + return NULL; + + return ucv_boolean_new(lmo_change_catalog( + lang ? ucv_string_get(lang) : "en") == 0); +} + +static void +uc_luci_get_translations_cb(uint32_t key, const char *val, int len, void *priv) { + uc_vm_t *vm = priv; + + uc_vm_stack_push(vm, ucv_get(uc_vm_stack_peek(vm, 0))); + uc_vm_stack_push(vm, ucv_uint64_new(key)); + uc_vm_stack_push(vm, ucv_string_new_length(val, (size_t)len)); + + if (uc_vm_call(vm, false, 2) == EXCEPTION_NONE) + ucv_put(uc_vm_stack_pop(vm)); +} + +static uc_value_t * +uc_luci_get_translations(uc_vm_t *vm, size_t nargs) { + lmo_iterate(uc_luci_get_translations_cb, vm); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_luci_translate(uc_vm_t *vm, size_t nargs) { + uc_value_t *key = uc_fn_arg(0); + uc_value_t *ctx = uc_fn_arg(1); + int trlen; + char *tr; + + if (ucv_type(key) != UC_STRING) + return NULL; + + if (ctx && ucv_type(ctx) != UC_STRING) + return NULL; + + if (lmo_translate_ctxt(ucv_string_get(key), ucv_string_length(key), + ucv_string_get(ctx), ucv_string_length(ctx), + &tr, &trlen) != 0) + return NULL; + + return ucv_string_new_length(tr, (size_t)trlen); +} + +static uc_value_t * +uc_luci_ntranslate(uc_vm_t *vm, size_t nargs) { + uc_value_t *cnt = uc_fn_arg(0); + uc_value_t *skey = uc_fn_arg(1); + uc_value_t *pkey = uc_fn_arg(2); + uc_value_t *ctx = uc_fn_arg(3); + int trlen; + char *tr; + + if (ucv_type(skey) != UC_STRING || ucv_type(pkey) != UC_STRING) + return NULL; + + if (ctx && ucv_type(ctx) != UC_STRING) + return NULL; + + if (lmo_translate_plural_ctxt(ucv_int64_get(cnt), + ucv_string_get(skey), ucv_string_length(skey), + ucv_string_get(pkey), ucv_string_length(pkey), + ucv_string_get(ctx), ucv_string_length(ctx), + &tr, &trlen) != 0) + return NULL; + + return ucv_string_new_length(tr, (size_t)trlen); +} + +static uc_value_t * +uc_luci_hash(uc_vm_t *vm, size_t nargs) { + uc_value_t *key = uc_fn_arg(0); + uc_value_t *init = uc_fn_arg(1); + + if (ucv_type(key) != UC_STRING) + return NULL; + + if (init && ucv_type(init) != UC_INTEGER) + return NULL; + + return ucv_uint64_new(sfh_hash(ucv_string_get(key), ucv_string_length(key), + init ? ucv_uint64_get(init) : ucv_string_length(key))); +} + + +/* user functions */ + +static uc_value_t * +uc_luci_getspnam(uc_vm_t *vm, size_t nargs) { + uc_value_t *name = uc_fn_arg(0), *rv; + struct spwd *s; + + if (ucv_type(name) != UC_STRING) + return NULL; + + s = getspnam(ucv_string_get(name)); + + if (!s) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "namp", ucv_string_new(s->sp_namp)); + ucv_object_add(rv, "pwdp", ucv_string_new(s->sp_pwdp)); + ucv_object_add(rv, "lstchg", ucv_int64_new(s->sp_lstchg)); + ucv_object_add(rv, "min", ucv_int64_new(s->sp_min)); + ucv_object_add(rv, "max", ucv_int64_new(s->sp_max)); + ucv_object_add(rv, "warn", ucv_int64_new(s->sp_warn)); + ucv_object_add(rv, "inact", ucv_int64_new(s->sp_inact)); + ucv_object_add(rv, "expire", ucv_int64_new(s->sp_expire)); + + return rv; +} + +static uc_value_t * +uc_luci_getpwnam(uc_vm_t *vm, size_t nargs) { + uc_value_t *name = uc_fn_arg(0), *rv; + struct passwd *p; + + if (ucv_type(name) != UC_STRING) + return NULL; + + p = getpwnam(ucv_string_get(name)); + + if (!p) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "name", ucv_string_new(p->pw_name)); + ucv_object_add(rv, "passwd", ucv_string_new(p->pw_passwd)); + ucv_object_add(rv, "uid", ucv_int64_new(p->pw_uid)); + ucv_object_add(rv, "gid", ucv_int64_new(p->pw_gid)); + ucv_object_add(rv, "gecos", ucv_string_new(p->pw_gecos)); + ucv_object_add(rv, "dir", ucv_string_new(p->pw_dir)); + ucv_object_add(rv, "shell", ucv_string_new(p->pw_shell)); + + return rv; +} + +static uc_value_t * +uc_luci_crypt(uc_vm_t *vm, size_t nargs) { + uc_value_t *phrase = uc_fn_arg(0); + uc_value_t *setting = uc_fn_arg(1); + char *hash; + + if (ucv_type(phrase) != UC_STRING || ucv_type(setting) != UC_STRING) + return NULL; + + errno = 0; + hash = crypt(ucv_string_get(phrase), ucv_string_get(setting)); + + if (hash == NULL || errno != 0) + return NULL; + + return ucv_string_new(hash); +} + +static uc_value_t * +uc_luci_getuid(uc_vm_t *vm, size_t nargs) { + return ucv_int64_new(getuid()); +} + +static uc_value_t * +uc_luci_getgid(uc_vm_t *vm, size_t nargs) { + return ucv_int64_new(getgid()); +} + +static uc_value_t * +uc_luci_setuid(uc_vm_t *vm, size_t nargs) { + uc_value_t *uid = uc_fn_arg(0); + + if (ucv_type(uid) != UC_INTEGER) + return NULL; + + return ucv_boolean_new(setuid(ucv_int64_get(uid)) == 0); +} + +static uc_value_t * +uc_luci_setgid(uc_vm_t *vm, size_t nargs) { + uc_value_t *gid = uc_fn_arg(0); + + if (ucv_type(gid) != UC_INTEGER) + return NULL; + + return ucv_boolean_new(setgid(ucv_int64_get(gid)) == 0); +} + + +/* misc functions */ + +static uc_value_t * +uc_luci_kill(uc_vm_t *vm, size_t nargs) { + uc_value_t *pid = uc_fn_arg(0); + uc_value_t *sig = uc_fn_arg(1); + + if (ucv_type(pid) != UC_INTEGER || ucv_type(sig) != UC_INTEGER) + return NULL; + + return ucv_boolean_new(kill(ucv_int64_get(pid), ucv_int64_get(sig)) == 0); +} + +static uc_value_t * +uc_luci_uname(uc_vm_t *vm, size_t nargs) { + struct utsname u; + uc_value_t *rv; + + if (uname(&u) == -1) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "sysname", ucv_string_new(u.sysname)); + ucv_object_add(rv, "nodename", ucv_string_new(u.nodename)); + ucv_object_add(rv, "release", ucv_string_new(u.release)); + ucv_object_add(rv, "version", ucv_string_new(u.version)); + ucv_object_add(rv, "machine", ucv_string_new(u.machine)); + + return rv; +} + +static uc_value_t * +uc_luci_sysinfo(uc_vm_t *vm, size_t nargs) { + uc_value_t *rv, *loads; + struct sysinfo i; + + if (sysinfo(&i) == -1) + return NULL; + + rv = ucv_object_new(vm); + loads = ucv_array_new_length(vm, 3); + + ucv_array_push(loads, ucv_uint64_new(i.loads[0])); + ucv_array_push(loads, ucv_uint64_new(i.loads[1])); + ucv_array_push(loads, ucv_uint64_new(i.loads[2])); + + ucv_object_add(rv, "uptime", ucv_int64_new(i.uptime)); + ucv_object_add(rv, "loads", loads); + ucv_object_add(rv, "totalram", ucv_uint64_new(i.totalram)); + ucv_object_add(rv, "freeram", ucv_uint64_new(i.freeram)); + ucv_object_add(rv, "sharedram", ucv_uint64_new(i.sharedram)); + ucv_object_add(rv, "bufferram", ucv_uint64_new(i.bufferram)); + ucv_object_add(rv, "totalswap", ucv_uint64_new(i.totalswap)); + ucv_object_add(rv, "freeswap", ucv_uint64_new(i.freeswap)); + ucv_object_add(rv, "procs", ucv_uint64_new(i.procs)); + ucv_object_add(rv, "totalhigh", ucv_uint64_new(i.totalhigh)); + ucv_object_add(rv, "freehigh", ucv_uint64_new(i.freehigh)); + ucv_object_add(rv, "mem_unit", ucv_uint64_new(i.mem_unit)); + + return rv; +} + +static uc_value_t * +uc_luci_statvfs(uc_vm_t *vm, size_t nargs) { + uc_value_t *path = uc_fn_arg(0), *rv; + struct statvfs s; + + if (ucv_type(path) != UC_STRING) + return NULL; + + if (statvfs(ucv_string_get(path), &s) == -1) + return NULL; + + rv = ucv_object_new(vm); + + ucv_object_add(rv, "bsize", ucv_uint64_new(s.f_bsize)); + ucv_object_add(rv, "frsize", ucv_uint64_new(s.f_frsize)); + + ucv_object_add(rv, "blocks", ucv_uint64_new(s.f_blocks)); + ucv_object_add(rv, "bfree", ucv_uint64_new(s.f_bfree)); + ucv_object_add(rv, "bavail", ucv_uint64_new(s.f_bavail)); + + ucv_object_add(rv, "files", ucv_uint64_new(s.f_files)); + ucv_object_add(rv, "ffree", ucv_uint64_new(s.f_ffree)); + ucv_object_add(rv, "favail", ucv_uint64_new(s.f_favail)); + + ucv_object_add(rv, "fsid", ucv_uint64_new(s.f_fsid)); + ucv_object_add(rv, "flag", ucv_uint64_new(s.f_flag)); + ucv_object_add(rv, "namemax", ucv_uint64_new(s.f_namemax)); + + return rv; +} + + +static const uc_function_list_t luci_fns[] = { + { "load_catalog", uc_luci_load_catalog }, + { "close_catalog", uc_luci_close_catalog }, + { "change_catalog", uc_luci_change_catalog }, + { "get_translations", uc_luci_get_translations }, + { "translate", uc_luci_translate }, + { "ntranslate", uc_luci_ntranslate }, + { "hash", uc_luci_hash }, + + { "getspnam", uc_luci_getspnam }, + { "getpwnam", uc_luci_getpwnam }, + { "crypt", uc_luci_crypt }, + { "getuid", uc_luci_getuid }, + { "setuid", uc_luci_setuid }, + { "getgid", uc_luci_getgid }, + { "setgid", uc_luci_setgid }, + + { "kill", uc_luci_kill }, + { "uname", uc_luci_uname }, + { "sysinfo", uc_luci_sysinfo }, + { "statvfs", uc_luci_statvfs }, +}; + + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, luci_fns); +} diff --git a/modules/luci-base/src/lib/plural_formula.y b/modules/luci-base/src/lib/plural_formula.y new file mode 100644 index 0000000000..1623f8b282 --- /dev/null +++ b/modules/luci-base/src/lib/plural_formula.y @@ -0,0 +1,43 @@ +%name pluralParse +%token_type {int} +%extra_argument {struct parse_state *s} + +%right T_QMARK. +%left T_OR. +%left T_AND. +%left T_EQ T_NE. +%left T_LT T_LE T_GT T_GE. +%left T_ADD T_SUB. +%left T_MUL T_DIV T_MOD. +%right T_NOT. +%nonassoc T_COLON T_N T_LPAREN T_RPAREN. + +%include { +#include + +struct parse_state { + int num; + int res; +}; +} + +input ::= expr(A). { s->res = A; } + +expr(A) ::= expr(B) T_QMARK expr(C) T_COLON expr(D). { A = B ? C : D; } +expr(A) ::= expr(B) T_OR expr(C). { A = B || C; } +expr(A) ::= expr(B) T_AND expr(C). { A = B && C; } +expr(A) ::= expr(B) T_EQ expr(C). { A = B == C; } +expr(A) ::= expr(B) T_NE expr(C). { A = B != C; } +expr(A) ::= expr(B) T_LT expr(C). { A = B < C; } +expr(A) ::= expr(B) T_LE expr(C). { A = B <= C; } +expr(A) ::= expr(B) T_GT expr(C). { A = B > C; } +expr(A) ::= expr(B) T_GE expr(C). { A = B >= C; } +expr(A) ::= expr(B) T_ADD expr(C). { A = B + C; } +expr(A) ::= expr(B) T_SUB expr(C). { A = B - C; } +expr(A) ::= expr(B) T_MUL expr(C). { A = B * C; } +expr(A) ::= expr(B) T_DIV expr(C). { A = B / C; } +expr(A) ::= expr(B) T_MOD expr(C). { A = B % C; } +expr(A) ::= T_NOT expr(B). { A = !B; } +expr(A) ::= T_N. { A = s->num; } +expr(A) ::= T_NUM(B). { A = B; } +expr(A) ::= T_LPAREN expr(B) T_RPAREN. { A = B; } diff --git a/modules/luci-base/src/mkversion.sh b/modules/luci-base/src/mkversion.sh deleted file mode 100755 index e2d02c1c74..0000000000 --- a/modules/luci-base/src/mkversion.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -cat < $1 -local pcall, dofile, _G = pcall, dofile, _G - -module "luci.version" - -if pcall(dofile, "/etc/openwrt_release") and _G.DISTRIB_DESCRIPTION then - distname = "" - distversion = _G.DISTRIB_DESCRIPTION - if _G.DISTRIB_REVISION then - distrevision = _G.DISTRIB_REVISION - if not distversion:find(distrevision,1,true) then - distversion = distversion .. " " .. distrevision - end - end -else - distname = "OpenWrt" - distversion = "Development Snapshot" -end - -luciname = "${3:-LuCI}" -luciversion = "${2:-Git}" -EOF diff --git a/modules/luci-base/src/plural_formula.y b/modules/luci-base/src/plural_formula.y deleted file mode 100644 index 1623f8b282..0000000000 --- a/modules/luci-base/src/plural_formula.y +++ /dev/null @@ -1,43 +0,0 @@ -%name pluralParse -%token_type {int} -%extra_argument {struct parse_state *s} - -%right T_QMARK. -%left T_OR. -%left T_AND. -%left T_EQ T_NE. -%left T_LT T_LE T_GT T_GE. -%left T_ADD T_SUB. -%left T_MUL T_DIV T_MOD. -%right T_NOT. -%nonassoc T_COLON T_N T_LPAREN T_RPAREN. - -%include { -#include - -struct parse_state { - int num; - int res; -}; -} - -input ::= expr(A). { s->res = A; } - -expr(A) ::= expr(B) T_QMARK expr(C) T_COLON expr(D). { A = B ? C : D; } -expr(A) ::= expr(B) T_OR expr(C). { A = B || C; } -expr(A) ::= expr(B) T_AND expr(C). { A = B && C; } -expr(A) ::= expr(B) T_EQ expr(C). { A = B == C; } -expr(A) ::= expr(B) T_NE expr(C). { A = B != C; } -expr(A) ::= expr(B) T_LT expr(C). { A = B < C; } -expr(A) ::= expr(B) T_LE expr(C). { A = B <= C; } -expr(A) ::= expr(B) T_GT expr(C). { A = B > C; } -expr(A) ::= expr(B) T_GE expr(C). { A = B >= C; } -expr(A) ::= expr(B) T_ADD expr(C). { A = B + C; } -expr(A) ::= expr(B) T_SUB expr(C). { A = B - C; } -expr(A) ::= expr(B) T_MUL expr(C). { A = B * C; } -expr(A) ::= expr(B) T_DIV expr(C). { A = B / C; } -expr(A) ::= expr(B) T_MOD expr(C). { A = B % C; } -expr(A) ::= T_NOT expr(B). { A = !B; } -expr(A) ::= T_N. { A = s->num; } -expr(A) ::= T_NUM(B). { A = B; } -expr(A) ::= T_LPAREN expr(B) T_RPAREN. { A = B; } diff --git a/modules/luci-base/src/po2lmo.c b/modules/luci-base/src/po2lmo.c index 5f398c266e..0a04e9ba17 100644 --- a/modules/luci-base/src/po2lmo.c +++ b/modules/luci-base/src/po2lmo.c @@ -16,7 +16,7 @@ * limitations under the License. */ -#include "template_lmo.h" +#include "lib/lmo.h" static void die(const char *msg) { @@ -169,8 +169,11 @@ static void print_msg(struct msg *msg, FILE *out) else snprintf(key, sizeof(key), "%s", msg->id); - key_id = sfh_hash(key, strlen(key)); - val_id = sfh_hash(msg->val[i], strlen(msg->val[i])); + len = strlen(key); + key_id = sfh_hash(key, len, len); + + len = strlen(msg->val[i]); + val_id = sfh_hash(msg->val[i], len, len); if (key_id != val_id) { n_entries++; diff --git a/modules/luci-base/src/template_lmo.c b/modules/luci-base/src/template_lmo.c deleted file mode 100644 index 8634bc4bf3..0000000000 --- a/modules/luci-base/src/template_lmo.c +++ /dev/null @@ -1,637 +0,0 @@ -/* - * lmo - Lua Machine Objects - Base functions - * - * Copyright (C) 2009-2010 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "template_lmo.h" -#include "plural_formula.h" - -/* - * Hash function from http://www.azillionmonkeys.com/qed/hash.html - * Copyright (C) 2004-2008 by Paul Hsieh - */ - -uint32_t sfh_hash(const char *data, int len) -{ - uint32_t hash = len, tmp; - int rem; - - if (len <= 0 || data == NULL) return 0; - - rem = len & 3; - len >>= 2; - - /* Main loop */ - for (;len > 0; len--) { - hash += sfh_get16(data); - tmp = (sfh_get16(data+2) << 11) ^ hash; - hash = (hash << 16) ^ tmp; - data += 2*sizeof(uint16_t); - hash += hash >> 11; - } - - /* Handle end cases */ - switch (rem) { - case 3: hash += sfh_get16(data); - hash ^= hash << 16; - hash ^= (signed char)data[sizeof(uint16_t)] << 18; - hash += hash >> 11; - break; - case 2: hash += sfh_get16(data); - hash ^= hash << 11; - hash += hash >> 17; - break; - case 1: hash += (signed char)*data; - hash ^= hash << 10; - hash += hash >> 1; - } - - /* Force "avalanching" of final 127 bits */ - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - - return hash; -} - -uint32_t lmo_canon_hash(const char *str, int len, - const char *ctx, int ctxlen, int plural) -{ - char res[4096]; - char *ptr, *end, prev; - int off; - - if (!str) - return 0; - - ptr = res; - end = res + sizeof(res); - - if (ctx) - { - for (prev = ' ', off = 0; off < ctxlen; prev = *ctx, off++, ctx++) - { - if (ptr >= end) - return 0; - - if (isspace(*ctx)) - { - if (!isspace(prev)) - *ptr++ = ' '; - } - else - { - *ptr++ = *ctx; - } - } - - if ((ptr > res) && isspace(*(ptr-1))) - ptr--; - - if (ptr >= end) - return 0; - - *ptr++ = '\1'; - } - - for (prev = ' ', off = 0; off < len; prev = *str, off++, str++) - { - if (ptr >= end) - return 0; - - if (isspace(*str)) - { - if (!isspace(prev)) - *ptr++ = ' '; - } - else - { - *ptr++ = *str; - } - } - - if ((ptr > res) && isspace(*(ptr-1))) - ptr--; - - if (plural > -1) - { - if (plural >= 100 || ptr + 3 >= end) - return 0; - - ptr += snprintf(ptr, 3, "\2%d", plural); - } - - return sfh_hash(res, ptr - res); -} - -lmo_archive_t * lmo_open(const char *file) -{ - int in = -1; - uint32_t idx_offset = 0; - struct stat s; - - lmo_archive_t *ar = NULL; - - if (stat(file, &s) == -1) - goto err; - - if ((in = open(file, O_RDONLY)) == -1) - goto err; - - if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL) - { - memset(ar, 0, sizeof(*ar)); - - ar->fd = in; - ar->size = s.st_size; - - fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC); - - if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED) - goto err; - - idx_offset = ntohl(*((const uint32_t *) - (ar->mmap + ar->size - sizeof(uint32_t)))); - - if (idx_offset >= ar->size) - goto err; - - ar->index = (lmo_entry_t *)(ar->mmap + idx_offset); - ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t); - ar->end = ar->mmap + ar->size; - - return ar; - } - -err: - if (in > -1) - close(in); - - if (ar != NULL) - { - if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) - munmap(ar->mmap, ar->size); - - free(ar); - } - - return NULL; -} - -void lmo_close(lmo_archive_t *ar) -{ - if (ar != NULL) - { - if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) - munmap(ar->mmap, ar->size); - - close(ar->fd); - free(ar); - - ar = NULL; - } -} - - -lmo_catalog_t *_lmo_catalogs = NULL; -lmo_catalog_t *_lmo_active_catalog = NULL; - -int lmo_load_catalog(const char *lang, const char *dir) -{ - DIR *dh = NULL; - char pattern[16]; - char path[PATH_MAX]; - struct dirent *de = NULL; - - lmo_archive_t *ar = NULL; - lmo_catalog_t *cat = NULL; - - if (!lmo_change_catalog(lang)) - return 0; - - if (!dir || !(dh = opendir(dir))) - goto err; - - if (!(cat = malloc(sizeof(*cat)))) - goto err; - - memset(cat, 0, sizeof(*cat)); - - snprintf(cat->lang, sizeof(cat->lang), "%s", lang); - snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang); - - while ((de = readdir(dh)) != NULL) - { - if (!fnmatch(pattern, de->d_name, 0)) - { - snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); - ar = lmo_open(path); - - if (ar) - { - ar->next = cat->archives; - cat->archives = ar; - } - } - } - - closedir(dh); - - cat->next = _lmo_catalogs; - _lmo_catalogs = cat; - - if (!_lmo_active_catalog) - _lmo_active_catalog = cat; - - return cat->archives ? 0 : -1; - -err: - if (dh) closedir(dh); - if (cat) free(cat); - - return -1; -} - -int lmo_change_catalog(const char *lang) -{ - lmo_catalog_t *cat; - - for (cat = _lmo_catalogs; cat; cat = cat->next) - { - if (!strncmp(cat->lang, lang, sizeof(cat->lang))) - { - _lmo_active_catalog = cat; - return 0; - } - } - - return -1; -} - -static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash) -{ - unsigned int m, l, r; - uint32_t k; - - l = 0; - r = ar->length - 1; - - while (1) - { - m = l + ((r - l) / 2); - - if (r < l) - break; - - k = ntohl(ar->index[m].key_id); - - if (k == hash) - return &ar->index[m]; - - if (k > hash) - { - if (!m) - break; - - r = m - 1; - } - else - { - l = m + 1; - } - } - - return NULL; -} - -void *pluralParseAlloc(void *(*)(size_t)); -void pluralParse(void *, int, int, void *); -void pluralParseFree(void *, void (*)(void *)); - -static int lmo_eval_plural(const char *expr, int len, int val) -{ - struct { int num; int res; } s = { .num = val, .res = -1 }; - const char *p = NULL; - void *pParser = NULL; - int t, n; - char c; - - while (len > 7) { - if (*expr == 'p') { - if (!strncmp(expr, "plural=", 7)) { - p = expr + 7; - len -= 7; - break; - } - } - - expr++; - len--; - } - - if (!p) - goto out; - - pParser = pluralParseAlloc(malloc); - - if (!pParser) - goto out; - - while (len-- > 0) { - c = *p++; - t = -1; - n = 0; - - switch (c) { - case ' ': - case '\t': - continue; - - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - t = T_NUM; - n = c - '0'; - - while (*p >= '0' && *p <= '9') { - n *= 10; - n += *p - '0'; - p++; - } - - break; - - case '=': - if (*p == '=') { - t = T_EQ; - p++; - } - - break; - - case '!': - if (*p == '=') { - t = T_NE; - p++; - } - else { - t = T_NOT; - } - - break; - - case '&': - if (*p == '&') { - t = T_AND; - p++; - } - - break; - - case '|': - if (*p == '|') { - t = T_OR; - p++; - } - - break; - - case '<': - if (*p == '=') { - t = T_LE; - p++; - } - else { - t = T_LT; - } - - break; - - case '>': - if (*p == '=') { - t = T_GE; - p++; - } - else { - t = T_GT; - } - - break; - - case '*': - t = T_MUL; - break; - - case '/': - t = T_DIV; - break; - - case '%': - t = T_MOD; - break; - - case '+': - t = T_ADD; - break; - - case '-': - t = T_SUB; - break; - - case 'n': - t = T_N; - break; - - case '?': - t = T_QMARK; - break; - - case ':': - t = T_COLON; - break; - - case '(': - t = T_LPAREN; - break; - - case ')': - t = T_RPAREN; - break; - - case ';': - case '\n': - case '\0': - t = 0; - break; - } - - /* syntax error */ - if (t < 0) - goto out; - - pluralParse(pParser, t, n, &s); - - /* eof */ - if (t == 0) - break; - } - - pluralParse(pParser, 0, 0, &s); - -out: - pluralParseFree(pParser, free); - - return s.res; -} - -int lmo_translate(const char *key, int keylen, char **out, int *outlen) -{ - return lmo_translate_ctxt(key, keylen, NULL, 0, out, outlen); -} - -int lmo_translate_ctxt(const char *key, int keylen, - const char *ctx, int ctxlen, - char **out, int *outlen) -{ - uint32_t hash; - lmo_entry_t *e; - lmo_archive_t *ar; - - if (!key || !_lmo_active_catalog) - return -2; - - hash = lmo_canon_hash(key, keylen, ctx, ctxlen, -1); - - if (hash > 0) - { - for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) - { - if ((e = lmo_find_entry(ar, hash)) != NULL) - { - *out = ar->mmap + ntohl(e->offset); - *outlen = ntohl(e->length); - return 0; - } - } - } - - return -1; -} - -int lmo_translate_plural(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - char **out, int *outlen) -{ - return lmo_translate_plural_ctxt(n, skey, skeylen, pkey, pkeylen, - NULL, 0, out, outlen); -} - -int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - const char *ctx, int ctxlen, - char **out, int *outlen) -{ - int pid = -1; - uint32_t hash; - lmo_entry_t *e; - lmo_archive_t *ar; - const char *plural_formula; - - if (!skey || !pkey || !_lmo_active_catalog) - return -2; - - for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) { - e = lmo_find_entry(ar, 0); - - if (e != NULL) { - pid = lmo_eval_plural(ar->mmap + ntohl(e->offset), ntohl(e->length), n); - break; - } - } - - if (pid == -1) - pid = (n != 1); - - hash = lmo_canon_hash(skey, skeylen, ctx, ctxlen, pid); - - if (hash == 0) - return -1; - - for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) - { - if ((e = lmo_find_entry(ar, hash)) != NULL) - { - *out = ar->mmap + ntohl(e->offset); - *outlen = ntohl(e->length); - return 0; - } - } - - if (n != 1) - { - *out = (char *)pkey; - *outlen = pkeylen; - } - else - { - *out = (char *)skey; - *outlen = skeylen; - } - - return 0; -} - -void lmo_iterate(lmo_iterate_cb_t cb, void *priv) -{ - unsigned int i; - lmo_entry_t *e; - lmo_archive_t *ar; - - if (!_lmo_active_catalog) - return; - - for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) - for (i = 0, e = &ar->index[0]; i < ar->length; e = &ar->index[++i]) - cb(ntohl(e->key_id), ar->mmap + ntohl(e->offset), ntohl(e->length), priv); -} - -void lmo_close_catalog(const char *lang) -{ - lmo_archive_t *ar, *next; - lmo_catalog_t *cat, *prev; - - for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next) - { - if (!strncmp(cat->lang, lang, sizeof(cat->lang))) - { - if (prev) - prev->next = cat->next; - else - _lmo_catalogs = cat->next; - - for (ar = cat->archives; ar; ar = next) - { - next = ar->next; - lmo_close(ar); - } - - free(cat); - break; - } - } -} diff --git a/modules/luci-base/src/template_lmo.h b/modules/luci-base/src/template_lmo.h deleted file mode 100644 index d6cba7bf49..0000000000 --- a/modules/luci-base/src/template_lmo.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * lmo - Lua Machine Objects - General header - * - * Copyright (C) 2009-2012 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _TEMPLATE_LMO_H_ -#define _TEMPLATE_LMO_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if (defined(__GNUC__) && defined(__i386__)) -#define sfh_get16(d) (*((const uint16_t *) (d))) -#else -#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ - +(uint32_t)(((const uint8_t *)(d))[0]) ) -#endif - - -struct lmo_entry { - uint32_t key_id; - uint32_t val_id; - uint32_t offset; - uint32_t length; -} __attribute__((packed)); - -typedef struct lmo_entry lmo_entry_t; - - -struct lmo_archive { - int fd; - int length; - uint32_t size; - lmo_entry_t *index; - char *mmap; - char *end; - struct lmo_archive *next; -}; - -typedef struct lmo_archive lmo_archive_t; - - -struct lmo_catalog { - char lang[6]; - struct lmo_archive *archives; - struct lmo_catalog *next; -}; - -typedef struct lmo_catalog lmo_catalog_t; - -typedef void (*lmo_iterate_cb_t)(uint32_t, const char *, int, void *); - -uint32_t sfh_hash(const char *data, int len); -uint32_t lmo_canon_hash(const char *data, int len, - const char *ctx, int ctxlen, int plural); - -lmo_archive_t * lmo_open(const char *file); -void lmo_close(lmo_archive_t *ar); - - -extern lmo_catalog_t *_lmo_catalogs; -extern lmo_catalog_t *_lmo_active_catalog; - -int lmo_load_catalog(const char *lang, const char *dir); -int lmo_change_catalog(const char *lang); -int lmo_translate(const char *key, int keylen, char **out, int *outlen); -int lmo_translate_ctxt(const char *key, int keylen, - const char *ctx, int ctxlen, char **out, int *outlen); -int lmo_translate_plural(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - char **out, int *outlen); -int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, - const char *pkey, int pkeylen, - const char *ctx, int ctxlen, - char **out, int *outlen); -void lmo_iterate(lmo_iterate_cb_t cb, void *priv); -void lmo_close_catalog(const char *lang); - -#endif diff --git a/modules/luci-base/src/template_lualib.c b/modules/luci-base/src/template_lualib.c deleted file mode 100644 index 4efd9f1de6..0000000000 --- a/modules/luci-base/src/template_lualib.c +++ /dev/null @@ -1,224 +0,0 @@ -/* - * LuCI Template - Lua binding - * - * Copyright (C) 2009 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "template_lualib.h" - -static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname) -{ - int lua_status, rv; - - if (!parser) - { - lua_pushnil(L); - lua_pushinteger(L, errno); - lua_pushstring(L, strerror(errno)); - return 3; - } - - lua_status = lua_load(L, template_reader, parser, chunkname); - - if (lua_status == 0) - rv = 1; - else - rv = template_error(L, parser); - - template_close(parser); - - return rv; -} - -int template_L_parse(lua_State *L) -{ - const char *file = luaL_checkstring(L, 1); - struct template_parser *parser = template_open(file); - - return template_L_do_parse(L, parser, file); -} - -int template_L_parse_string(lua_State *L) -{ - size_t len; - const char *str = luaL_checklstring(L, 1, &len); - struct template_parser *parser = template_string(str, len); - - return template_L_do_parse(L, parser, "[string]"); -} - -int template_L_utf8(lua_State *L) -{ - size_t len = 0; - const char *str = luaL_checklstring(L, 1, &len); - char *res = utf8(str, len); - - if (res != NULL) - { - lua_pushstring(L, res); - free(res); - - return 1; - } - - return 0; -} - -int template_L_pcdata(lua_State *L) -{ - size_t len = 0; - const char *str = luaL_checklstring(L, 1, &len); - char *res = pcdata(str, len); - - if (res != NULL) - { - lua_pushstring(L, res); - free(res); - - return 1; - } - - return 0; -} - -int template_L_striptags(lua_State *L) -{ - size_t len = 0; - const char *str = luaL_checklstring(L, 1, &len); - char *res = striptags(str, len); - - if (res != NULL) - { - lua_pushstring(L, res); - free(res); - - return 1; - } - - return 0; -} - -static int template_L_load_catalog(lua_State *L) { - const char *lang = luaL_optstring(L, 1, "en"); - const char *dir = luaL_optstring(L, 2, NULL); - lua_pushboolean(L, !lmo_load_catalog(lang, dir)); - return 1; -} - -static int template_L_close_catalog(lua_State *L) { - const char *lang = luaL_optstring(L, 1, "en"); - lmo_close_catalog(lang); - return 0; -} - -static int template_L_change_catalog(lua_State *L) { - const char *lang = luaL_optstring(L, 1, "en"); - lua_pushboolean(L, !lmo_change_catalog(lang)); - return 1; -} - -static void template_L_get_translations_cb(uint32_t key, const char *val, int len, void *priv) { - lua_State *L = priv; - char hex[9]; - - luaL_checktype(L, 1, LUA_TFUNCTION); - snprintf(hex, sizeof(hex), "%08x", key); - - lua_pushvalue(L, 1); - lua_pushstring(L, hex); - lua_pushlstring(L, val, len); - lua_call(L, 2, 0); -} - -static int template_L_get_translations(lua_State *L) { - lmo_iterate(template_L_get_translations_cb, L); - return 0; -} - -static int template_L_translate(lua_State *L) { - size_t len, ctxlen = 0; - char *tr; - int trlen; - const char *key = luaL_checklstring(L, 1, &len); - const char *ctx = luaL_optlstring(L, 2, NULL, &ctxlen); - - switch (lmo_translate_ctxt(key, len, ctx, ctxlen, &tr, &trlen)) - { - case 0: - lua_pushlstring(L, tr, trlen); - return 1; - - case -1: - return 0; - } - - lua_pushnil(L); - lua_pushstring(L, "no catalog loaded"); - return 2; -} - -static int template_L_ntranslate(lua_State *L) { - size_t slen, plen, ctxlen = 0; - char *tr; - int trlen; - int n = luaL_checkinteger(L, 1); - const char *skey = luaL_checklstring(L, 2, &slen); - const char *pkey = luaL_checklstring(L, 3, &plen); - const char *ctx = luaL_optlstring(L, 4, NULL, &ctxlen); - - switch (lmo_translate_plural_ctxt(n, skey, slen, pkey, plen, ctx, ctxlen, &tr, &trlen)) - { - case 0: - lua_pushlstring(L, tr, trlen); - return 1; - - case -1: - return 0; - } - - lua_pushnil(L); - lua_pushstring(L, "no catalog loaded"); - return 2; -} - -static int template_L_hash(lua_State *L) { - size_t len; - const char *key = luaL_checklstring(L, 1, &len); - lua_pushinteger(L, sfh_hash(key, len)); - return 1; -} - - -/* module table */ -static const luaL_reg R[] = { - { "parse", template_L_parse }, - { "parse_string", template_L_parse_string }, - { "utf8", template_L_utf8 }, - { "pcdata", template_L_pcdata }, - { "striptags", template_L_striptags }, - { "load_catalog", template_L_load_catalog }, - { "close_catalog", template_L_close_catalog }, - { "change_catalog", template_L_change_catalog }, - { "get_translations", template_L_get_translations }, - { "translate", template_L_translate }, - { "ntranslate", template_L_ntranslate }, - { "hash", template_L_hash }, - { NULL, NULL } -}; - -LUALIB_API int luaopen_luci_template_parser(lua_State *L) { - luaL_register(L, TEMPLATE_LUALIB_META, R); - return 1; -} diff --git a/modules/luci-base/src/template_lualib.h b/modules/luci-base/src/template_lualib.h deleted file mode 100644 index ff7746d158..0000000000 --- a/modules/luci-base/src/template_lualib.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * LuCI Template - Lua library header - * - * Copyright (C) 2009 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _TEMPLATE_LUALIB_H_ -#define _TEMPLATE_LUALIB_H_ - -#include "template_parser.h" -#include "template_utils.h" -#include "template_lmo.h" - -#define TEMPLATE_LUALIB_META "template.parser" - -LUALIB_API int luaopen_luci_template_parser(lua_State *L); - -#endif diff --git a/modules/luci-base/src/template_parser.c b/modules/luci-base/src/template_parser.c deleted file mode 100644 index 0ef08c63d2..0000000000 --- a/modules/luci-base/src/template_parser.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - * LuCI Template - Parser implementation - * - * Copyright (C) 2009-2012 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "template_parser.h" -#include "template_utils.h" -#include "template_lmo.h" - - -/* leading and trailing code for different types */ -const char *gen_code[9][2] = { - { NULL, NULL }, - { "write(\"", "\")" }, - { NULL, NULL }, - { "write(tostring(", " or \"\"))" }, - { "include(\"", "\")" }, - { "write(\"", "\")" }, - { "write(\"", "\")" }, - { NULL, " " }, - { NULL, NULL }, -}; - -/* Simple strstr() like function that takes len arguments for both haystack and needle. */ -static char *strfind(char *haystack, int hslen, const char *needle, int ndlen) -{ - int match = 0; - int i, j; - - for( i = 0; i < hslen; i++ ) - { - if( haystack[i] == needle[0] ) - { - match = ((ndlen == 1) || ((i + ndlen) <= hslen)); - - for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ ) - { - if( haystack[i+j] != needle[j] ) - { - match = 0; - break; - } - } - - if( match ) - return &haystack[i]; - } - } - - return NULL; -} - -struct template_parser * template_open(const char *file) -{ - struct stat s; - struct template_parser *parser; - - if (!(parser = malloc(sizeof(*parser)))) - goto err; - - memset(parser, 0, sizeof(*parser)); - parser->fd = -1; - parser->file = file; - - if (stat(file, &s)) - goto err; - - if ((parser->fd = open(file, O_RDONLY)) < 0) - goto err; - - parser->size = s.st_size; - parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE, - parser->fd, 0); - - if (parser->data != MAP_FAILED) - { - parser->off = parser->data; - parser->cur_chunk.type = T_TYPE_INIT; - parser->cur_chunk.s = parser->data; - parser->cur_chunk.e = parser->data; - - return parser; - } - -err: - template_close(parser); - return NULL; -} - -struct template_parser * template_string(const char *str, uint32_t len) -{ - struct template_parser *parser; - - if (!str) { - errno = EINVAL; - goto err; - } - - if (!(parser = malloc(sizeof(*parser)))) - goto err; - - memset(parser, 0, sizeof(*parser)); - parser->fd = -1; - - parser->size = len; - parser->data = (char*)str; - - parser->off = parser->data; - parser->cur_chunk.type = T_TYPE_INIT; - parser->cur_chunk.s = parser->data; - parser->cur_chunk.e = parser->data; - - return parser; - -err: - template_close(parser); - return NULL; -} - -void template_close(struct template_parser *parser) -{ - if (!parser) - return; - - if (parser->gc != NULL) - free(parser->gc); - - /* if file is not set, we were parsing a string */ - if (parser->file) { - if ((parser->data != NULL) && (parser->data != MAP_FAILED)) - munmap(parser->data, parser->size); - - if (parser->fd >= 0) - close(parser->fd); - } - - free(parser); -} - -void template_text(struct template_parser *parser, const char *e) -{ - const char *s = parser->off; - - if (s < (parser->data + parser->size)) - { - if (parser->strip_after) - { - while ((s <= e) && isspace(*s)) - s++; - } - - parser->cur_chunk.type = T_TYPE_TEXT; - } - else - { - parser->cur_chunk.type = T_TYPE_EOF; - } - - parser->cur_chunk.line = parser->line; - parser->cur_chunk.s = s; - parser->cur_chunk.e = e; -} - -void template_code(struct template_parser *parser, const char *e) -{ - const char *s = parser->off; - - parser->strip_before = 0; - parser->strip_after = 0; - - if (*s == '-') - { - parser->strip_before = 1; - for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++); - } - - if (*(e-1) == '-') - { - parser->strip_after = 1; - for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--); - } - - switch (*s) - { - /* comment */ - case '#': - s++; - parser->cur_chunk.type = T_TYPE_COMMENT; - break; - - /* include */ - case '+': - s++; - parser->cur_chunk.type = T_TYPE_INCLUDE; - break; - - /* translate */ - case ':': - s++; - parser->cur_chunk.type = T_TYPE_I18N; - break; - - /* translate raw */ - case '_': - s++; - parser->cur_chunk.type = T_TYPE_I18N_RAW; - break; - - /* expr */ - case '=': - s++; - parser->cur_chunk.type = T_TYPE_EXPR; - break; - - /* code */ - default: - parser->cur_chunk.type = T_TYPE_CODE; - break; - } - - parser->cur_chunk.line = parser->line; - parser->cur_chunk.s = s; - parser->cur_chunk.e = e; -} - -static const char * -template_format_chunk(struct template_parser *parser, size_t *sz) -{ - const char *s, *p; - const char *head, *tail; - struct template_chunk *c = &parser->prv_chunk; - struct template_buffer *buf; - - *sz = 0; - s = parser->gc = NULL; - - if (parser->strip_before && c->type == T_TYPE_TEXT) - { - while ((c->e > c->s) && isspace(*(c->e - 1))) - c->e--; - } - - /* empty chunk */ - if (c->s == c->e) - { - if (c->type == T_TYPE_EOF) - { - *sz = 0; - s = NULL; - } - else - { - *sz = 1; - s = " "; - } - } - - /* format chunk */ - else if ((buf = buf_init(c->e - c->s)) != NULL) - { - if ((head = gen_code[c->type][0]) != NULL) - buf_append(buf, head, strlen(head)); - - switch (c->type) - { - case T_TYPE_TEXT: - luastr_escape(buf, c->s, c->e - c->s, 0); - break; - - case T_TYPE_EXPR: - buf_append(buf, c->s, c->e - c->s); - for (p = c->s; p < c->e; p++) - parser->line += (*p == '\n'); - break; - - case T_TYPE_INCLUDE: - luastr_escape(buf, c->s, c->e - c->s, 0); - break; - - case T_TYPE_I18N: - luastr_translate(buf, c->s, c->e - c->s, 1); - break; - - case T_TYPE_I18N_RAW: - luastr_translate(buf, c->s, c->e - c->s, 0); - break; - - case T_TYPE_CODE: - buf_append(buf, c->s, c->e - c->s); - for (p = c->s; p < c->e; p++) - parser->line += (*p == '\n'); - break; - } - - if ((tail = gen_code[c->type][1]) != NULL) - buf_append(buf, tail, strlen(tail)); - - *sz = buf_length(buf); - s = parser->gc = buf_destroy(buf); - - if (!*sz) - { - *sz = 1; - s = " "; - } - } - - return s; -} - -const char *template_reader(lua_State *L, void *ud, size_t *sz) -{ - struct template_parser *parser = ud; - int rem = parser->size - (parser->off - parser->data); - char *tag; - - parser->prv_chunk = parser->cur_chunk; - - /* free previous string */ - if (parser->gc) - { - free(parser->gc); - parser->gc = NULL; - } - - /* before tag */ - if (!parser->in_expr) - { - if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL) - { - template_text(parser, tag); - parser->off = tag + 2; - parser->in_expr = 1; - } - else - { - template_text(parser, parser->data + parser->size); - parser->off = parser->data + parser->size; - } - } - - /* inside tag */ - else - { - if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL) - { - template_code(parser, tag); - parser->off = tag + 2; - parser->in_expr = 0; - } - else - { - /* unexpected EOF */ - template_code(parser, parser->data + parser->size); - - *sz = 1; - return "\033"; - } - } - - return template_format_chunk(parser, sz); -} - -int template_error(lua_State *L, struct template_parser *parser) -{ - const char *err = luaL_checkstring(L, -1); - const char *off = parser->prv_chunk.s; - const char *ptr; - char msg[1024]; - int line = 0; - int chunkline = 0; - - if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL) - { - chunkline = atoi(ptr + 2) - parser->prv_chunk.line; - - while (*ptr) - { - if (*ptr++ == ' ') - { - err = ptr; - break; - } - } - } - - if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL) - { - off = parser->data + parser->size; - err = "'%>' expected before end of file"; - chunkline = 0; - } - - for (ptr = parser->data; ptr < off; ptr++) - if (*ptr == '\n') - line++; - - snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s", - parser->file ? parser->file : "[string]", line + chunkline, err ? err : "(unknown error)"); - - lua_pushnil(L); - lua_pushinteger(L, line + chunkline); - lua_pushstring(L, msg); - - return 3; -} diff --git a/modules/luci-base/src/template_parser.h b/modules/luci-base/src/template_parser.h deleted file mode 100644 index 2415e87079..0000000000 --- a/modules/luci-base/src/template_parser.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * LuCI Template - Parser header - * - * Copyright (C) 2009 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _TEMPLATE_PARSER_H_ -#define _TEMPLATE_PARSER_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -/* code types */ -#define T_TYPE_INIT 0 -#define T_TYPE_TEXT 1 -#define T_TYPE_COMMENT 2 -#define T_TYPE_EXPR 3 -#define T_TYPE_INCLUDE 4 -#define T_TYPE_I18N 5 -#define T_TYPE_I18N_RAW 6 -#define T_TYPE_CODE 7 -#define T_TYPE_EOF 8 - - -struct template_chunk { - const char *s; - const char *e; - int type; - int line; -}; - -/* parser state */ -struct template_parser { - int fd; - uint32_t size; - char *data; - char *off; - char *gc; - int line; - int in_expr; - int strip_before; - int strip_after; - struct template_chunk prv_chunk; - struct template_chunk cur_chunk; - const char *file; -}; - -struct template_parser * template_open(const char *file); -struct template_parser * template_string(const char *str, uint32_t len); -void template_close(struct template_parser *parser); - -const char *template_reader(lua_State *L, void *ud, size_t *sz); -int template_error(lua_State *L, struct template_parser *parser); - -#endif diff --git a/modules/luci-base/src/template_utils.c b/modules/luci-base/src/template_utils.c deleted file mode 100644 index 8580405e32..0000000000 --- a/modules/luci-base/src/template_utils.c +++ /dev/null @@ -1,500 +0,0 @@ -/* - * LuCI Template - Utility functions - * - * Copyright (C) 2010 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "template_utils.h" -#include "template_lmo.h" - -/* initialize a buffer object */ -struct template_buffer * buf_init(int size) -{ - struct template_buffer *buf; - - if (size <= 0) - size = 1024; - - buf = (struct template_buffer *)malloc(sizeof(struct template_buffer)); - - if (buf != NULL) - { - buf->fill = 0; - buf->size = size; - buf->data = malloc(buf->size); - - if (buf->data != NULL) - { - buf->dptr = buf->data; - buf->data[0] = 0; - - return buf; - } - - free(buf); - } - - return NULL; -} - -/* grow buffer */ -int buf_grow(struct template_buffer *buf, int size) -{ - unsigned int off = (buf->dptr - buf->data); - char *data; - - if (size <= 0) - size = 1024; - - data = realloc(buf->data, buf->size + size); - - if (data != NULL) - { - buf->data = data; - buf->dptr = data + off; - buf->size += size; - - return buf->size; - } - - return 0; -} - -/* put one char into buffer object */ -int buf_putchar(struct template_buffer *buf, char c) -{ - if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) ) - return 0; - - *(buf->dptr++) = c; - *(buf->dptr) = 0; - - buf->fill++; - return 1; -} - -/* append data to buffer */ -int buf_append(struct template_buffer *buf, const char *s, int len) -{ - if ((buf->fill + len + 1) >= buf->size) - { - if (!buf_grow(buf, len + 1)) - return 0; - } - - memcpy(buf->dptr, s, len); - buf->fill += len; - buf->dptr += len; - - *(buf->dptr) = 0; - - return len; -} - -/* read buffer length */ -int buf_length(struct template_buffer *buf) -{ - return buf->fill; -} - -/* destroy buffer object and return pointer to data */ -char * buf_destroy(struct template_buffer *buf) -{ - char *data = buf->data; - - free(buf); - return data; -} - - -/* calculate the number of expected continuation chars */ -static inline int mb_num_chars(unsigned char c) -{ - if ((c & 0xE0) == 0xC0) - return 2; - else if ((c & 0xF0) == 0xE0) - return 3; - else if ((c & 0xF8) == 0xF0) - return 4; - else if ((c & 0xFC) == 0xF8) - return 5; - else if ((c & 0xFE) == 0xFC) - return 6; - - return 1; -} - -/* test whether the given byte is a valid continuation char */ -static inline int mb_is_cont(unsigned char c) -{ - return ((c >= 0x80) && (c <= 0xBF)); -} - -/* test whether the byte sequence at the given pointer with the given - * length is the shortest possible representation of the code point */ -static inline int mb_is_shortest(unsigned char *s, int n) -{ - switch (n) - { - case 2: - /* 1100000x (10xxxxxx) */ - return !(((*s >> 1) == 0x60) && - ((*(s+1) >> 6) == 0x02)); - - case 3: - /* 11100000 100xxxxx (10xxxxxx) */ - return !((*s == 0xE0) && - ((*(s+1) >> 5) == 0x04) && - ((*(s+2) >> 6) == 0x02)); - - case 4: - /* 11110000 1000xxxx (10xxxxxx 10xxxxxx) */ - return !((*s == 0xF0) && - ((*(s+1) >> 4) == 0x08) && - ((*(s+2) >> 6) == 0x02) && - ((*(s+3) >> 6) == 0x02)); - - case 5: - /* 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx) */ - return !((*s == 0xF8) && - ((*(s+1) >> 3) == 0x10) && - ((*(s+2) >> 6) == 0x02) && - ((*(s+3) >> 6) == 0x02) && - ((*(s+4) >> 6) == 0x02)); - - case 6: - /* 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) */ - return !((*s == 0xF8) && - ((*(s+1) >> 2) == 0x20) && - ((*(s+2) >> 6) == 0x02) && - ((*(s+3) >> 6) == 0x02) && - ((*(s+4) >> 6) == 0x02) && - ((*(s+5) >> 6) == 0x02)); - } - - return 1; -} - -/* test whether the byte sequence at the given pointer with the given - * length is an UTF-16 surrogate */ -static inline int mb_is_surrogate(unsigned char *s, int n) -{ - return ((n == 3) && (*s == 0xED) && (*(s+1) >= 0xA0) && (*(s+1) <= 0xBF)); -} - -/* test whether the byte sequence at the given pointer with the given - * length is an illegal UTF-8 code point */ -static inline int mb_is_illegal(unsigned char *s, int n) -{ - return ((n == 3) && (*s == 0xEF) && (*(s+1) == 0xBF) && - (*(s+2) >= 0xBE) && (*(s+2) <= 0xBF)); -} - - -/* scan given source string, validate UTF-8 sequence and store result - * in given buffer object */ -static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf) -{ - unsigned char *ptr = *s; - unsigned int o = 0, v, n; - - /* ascii byte without null */ - if ((*(ptr+0) >= 0x01) && (*(ptr+0) <= 0x7F)) - { - if (!buf_putchar(buf, *ptr++)) - return 0; - - o = 1; - } - - /* multi byte sequence */ - else if ((n = mb_num_chars(*ptr)) > 1) - { - /* count valid chars */ - for (v = 1; (v <= n) && ((o+v) < l) && mb_is_cont(*(ptr+v)); v++); - - switch (n) - { - case 6: - case 5: - /* five and six byte sequences are always invalid */ - if (!buf_putchar(buf, '?')) - return 0; - - break; - - default: - /* if the number of valid continuation bytes matches the - * expected number and if the sequence is legal, copy - * the bytes to the destination buffer */ - if ((v == n) && mb_is_shortest(ptr, n) && - !mb_is_surrogate(ptr, n) && !mb_is_illegal(ptr, n)) - { - /* copy sequence */ - if (!buf_append(buf, (char *)ptr, n)) - return 0; - } - - /* the found sequence is illegal, skip it */ - else - { - /* invalid sequence */ - if (!buf_putchar(buf, '?')) - return 0; - } - - break; - } - - /* advance beyond the last found valid continuation char */ - o = v; - ptr += v; - } - - /* invalid byte (0x00) */ - else - { - if (!buf_putchar(buf, '?')) /* or 0xEF, 0xBF, 0xBD */ - return 0; - - o = 1; - ptr++; - } - - *s = ptr; - return o; -} - -/* sanitize given string and replace all invalid UTF-8 sequences with "?" */ -char * utf8(const char *s, unsigned int l) -{ - struct template_buffer *buf = buf_init(l); - unsigned char *ptr = (unsigned char *)s; - unsigned int v, o; - - if (!buf) - return NULL; - - for (o = 0; o < l; o++) - { - /* ascii char */ - if ((*ptr >= 0x01) && (*ptr <= 0x7F)) - { - if (!buf_putchar(buf, (char)*ptr++)) - break; - } - - /* invalid byte or multi byte sequence */ - else - { - if (!(v = _validate_utf8(&ptr, l - o, buf))) - break; - - o += (v - 1); - } - } - - return buf_destroy(buf); -} - -/* Sanitize given string and strip all invalid XML bytes - * Validate UTF-8 sequences - * Escape XML control chars */ -char * pcdata(const char *s, unsigned int l) -{ - struct template_buffer *buf = buf_init(l); - unsigned char *ptr = (unsigned char *)s; - unsigned int o, v; - char esq[8]; - int esl; - - if (!buf) - return NULL; - - for (o = 0; o < l; o++) - { - /* Invalid XML bytes */ - if (((*ptr >= 0x00) && (*ptr <= 0x08)) || - ((*ptr >= 0x0B) && (*ptr <= 0x0C)) || - ((*ptr >= 0x0E) && (*ptr <= 0x1F)) || - (*ptr == 0x7F)) - { - ptr++; - } - - /* Escapes */ - else if ((*ptr == 0x26) || - (*ptr == 0x27) || - (*ptr == 0x22) || - (*ptr == 0x3C) || - (*ptr == 0x3E)) - { - esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); - - if (!buf_append(buf, esq, esl)) - break; - - ptr++; - } - - /* ascii char */ - else if (*ptr <= 0x7F) - { - buf_putchar(buf, (char)*ptr++); - } - - /* multi byte sequence */ - else - { - if (!(v = _validate_utf8(&ptr, l - o, buf))) - break; - - o += (v - 1); - } - } - - return buf_destroy(buf); -} - -char * striptags(const char *s, unsigned int l) -{ - struct template_buffer *buf = buf_init(l); - unsigned char *ptr = (unsigned char *)s; - unsigned char *end = ptr + l; - unsigned char *tag; - unsigned char prev; - char esq[8]; - int esl; - - for (prev = ' '; ptr < end; ptr++) - { - if ((*ptr == '<') && ((ptr + 2) < end) && - ((*(ptr + 1) == '/') || isalpha(*(ptr + 1)))) - { - for (tag = ptr; tag < end; tag++) - { - if (*tag == '>') - { - if (!isspace(prev)) - buf_putchar(buf, ' '); - - ptr = tag; - prev = ' '; - break; - } - } - } - else if (isspace(*ptr)) - { - if (!isspace(prev)) - buf_putchar(buf, *ptr); - - prev = *ptr; - } - else - { - switch(*ptr) - { - case '"': - case '\'': - case '<': - case '>': - case '&': - esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); - buf_append(buf, esq, esl); - break; - - default: - buf_putchar(buf, *ptr); - break; - } - - prev = *ptr; - } - } - - return buf_destroy(buf); -} - -void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, - int escape_xml) -{ - int esl; - char esq[8]; - char *ptr; - - for (ptr = (char *)s; ptr < (s + l); ptr++) - { - switch (*ptr) - { - case '\\': - buf_append(out, "\\\\", 2); - break; - - case '"': - if (escape_xml) - buf_append(out, """, 5); - else - buf_append(out, "\\\"", 2); - break; - - case '\n': - buf_append(out, "\\n", 2); - break; - - case '\'': - case '&': - case '<': - case '>': - if (escape_xml) - { - esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); - buf_append(out, esq, esl); - break; - } - - default: - buf_putchar(out, *ptr); - } - } -} - -void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, - int escape_xml) -{ - int trlen, idlen = l, ctxtlen = 0, esc = 0; - const char *p, *msgid = s, *msgctxt = NULL; - char *tr; - - for (p = s; p < s + l; p++) { - if (esc) { - esc = 0; - } - else if (*p == '\\') { - esc = 1; - } - else if (*p == '|') { - idlen = p - s; - msgctxt = p + 1; - ctxtlen = s + l - msgctxt; - break; - } - } - - if (!lmo_translate_ctxt(msgid, idlen, msgctxt, ctxtlen, &tr, &trlen)) - luastr_escape(out, tr, trlen, escape_xml); - else - luastr_escape(out, s, l, escape_xml); -} diff --git a/modules/luci-base/src/template_utils.h b/modules/luci-base/src/template_utils.h deleted file mode 100644 index 32a79f93bc..0000000000 --- a/modules/luci-base/src/template_utils.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * LuCI Template - Utility header - * - * Copyright (C) 2010-2012 Jo-Philipp Wich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _TEMPLATE_UTILS_H_ -#define _TEMPLATE_UTILS_H_ - -#include -#include -#include - - -/* buffer object */ -struct template_buffer { - char *data; - char *dptr; - unsigned int size; - unsigned int fill; -}; - -struct template_buffer * buf_init(int size); -int buf_grow(struct template_buffer *buf, int size); -int buf_putchar(struct template_buffer *buf, char c); -int buf_append(struct template_buffer *buf, const char *s, int len); -int buf_length(struct template_buffer *buf); -char * buf_destroy(struct template_buffer *buf); - -char * utf8(const char *s, unsigned int l); -char * pcdata(const char *s, unsigned int l); -char * striptags(const char *s, unsigned int l); - -void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, int escape_xml); -void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, int escape_xml); - -#endif diff --git a/modules/luci-base/ucode/controller/admin/index.uc b/modules/luci-base/ucode/controller/admin/index.uc new file mode 100644 index 0000000000..16a74abc46 --- /dev/null +++ b/modules/luci-base/ucode/controller/admin/index.uc @@ -0,0 +1,158 @@ +// Copyright 2022 Jo-Philipp Wich +// Licensed to the public under the Apache License 2.0. + +import { load_catalog, change_catalog, get_translations } from 'luci.core'; + +const ubus_types = [ + null, + 'array', + 'object', + 'string', + null, // INT64 + 'number', + null, // INT16, + 'boolean', + 'double' +]; + + +function ubus_reply(id, data, code, errmsg) { + const reply = { jsonrpc: '2.0', id }; + + if (errmsg) + reply.error = { code, message: errmsg }; + else if (type(code) == 'object') + reply.result = code; + else + reply.result = [ code, data ]; + + return reply; +} + +function ubus_access(sid, obj, fun) { + return (ubus.call('session', 'access', { + ubus_rpc_session: sid, + scope: 'ubus', + object: obj, + function: fun + })?.access == true); +} + +function ubus_request(req) { + if (type(req?.method) != 'string' || req?.jsonrpc != '2.0' || req?.id == null) + return ubus_reply(null, null, -32600, 'Invalid request'); + + if (req.method == 'call') { + if (type(req?.params) != 'array' || length(req.params) < 3) + return ubus_reply(null, null, -32600, 'Invalid parameters'); + + let sid = req.params[0], + obj = req.params[1], + fun = req.params[2], + arg = req.params[3] ?? {}; + + if (type(arg) != 'object' || exists(arg, 'ubus_rpc_session')) + return ubus_reply(req.id, null, -32602, 'Invalid parameters'); + + if (sid == '00000000000000000000000000000000' && ctx.authsession) + sid = ctx.authsession; + + if (!ubus_access(sid, obj, fun)) + return ubus_reply(req.id, null, -32002, 'Access denied'); + + arg.ubus_rpc_session = sid; + + + // clear error + ubus.error(); + + const res = ubus.call(obj, fun, arg); + + return ubus_reply(req.id, res, ubus.error(true) ?? 0); + } + + if (req.method == 'list') { + if (req?.params == null || (type(req.params) == 'array' && length(req.params) == 0)) { + return ubus_reply(req.id, null, ubus.list()); + } + else if (type(req.params) == 'array') { + const rv = {}; + + for (let param in req.params) { + if (type(param) != 'string') + return ubus_reply(req.id, null, -32602, 'Invalid parameters'); + + for (let m, p in ubus.list(param)?.[0]) { + for (let pn, pt in p) { + rv[param] ??= {}; + rv[param][m] ??= {}; + rv[param][m][pn] = ubus_types[pt] ?? 'unknown'; + } + } + } + + return ubus_reply(req.id, null, rv); + } + else { + return ubus_reply(req.id, null, -32602, 'Invalid parameters') + } + } + + return ubus_reply(req.id, null, -32601, 'Method not found') +} + + +return { + action_ubus: function() { + let request; + + try { request = json(http.content()); } + catch { request = null; } + + http.prepare_content('application/json; charset=UTF-8'); + + if (type(request) == 'object') + http.write_json(ubus_request(request)); + else if (type(request) == 'array') + http.write_json(map(request, ubus_request)); + else + http.write_json(ubus_reply(null, null, -32700, 'Parse error')) + }, + + action_translations: function(reqlang) { + if (reqlang != null && reqlang != dispatcher.lang) { + load_catalog(reqlang, '/usr/lib/lua/luci/i18n'); + change_catalog(reqlang); + } + + http.prepare_content('application/javascript; charset=UTF-8'); + http.write('window.TR={'); + + get_translations((key, val) => http.write(sprintf('"%08x":%J,', key, val))); + + http.write('};'); + }, + + action_logout: function() { + const url = dispatcher.build_url(); + + if (ctx.authsession) { + ubus.call('session', 'destroy', { ubus_rpc_session: ctx.authsession }); + + if (http.getenv('HTTPS') == 'on') + http.header('Set-Cookie', `sysauth_https=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=${url}`); + + http.header('Set-Cookie', `sysauth_http=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=${url}`); + } + + http.redirect(url); + }, + + action_menu: function() { + const session = dispatcher.is_authenticated({ methods: [ 'cookie:sysauth_https', 'cookie:sysauth_http' ] }); + const menu = dispatcher.menu_json(session?.acls ?? {}) ?? {}; + + http.prepare_content('application/json; charset=UTF-8'); + http.write_json(menu); + } +}; diff --git a/modules/luci-base/ucode/controller/admin/uci.uc b/modules/luci-base/ucode/controller/admin/uci.uc new file mode 100644 index 0000000000..c38a42b10b --- /dev/null +++ b/modules/luci-base/ucode/controller/admin/uci.uc @@ -0,0 +1,150 @@ +// Copyright 2022 Jo-Philipp Wich +// Licensed to the public under the Apache License 2.0. + +import { STATUS_NO_DATA, STATUS_PERMISSION_DENIED } from 'ubus'; + +let last_ubus_error; + +const ubus_error_map = [ + 200, 'OK', + 400, 'Invalid command', + 400, 'Invalid argument', + 404, 'Method not found', + 404, 'Not found', + 204, 'No data', + 403, 'Permission denied', + 504, 'Timeout', + 500, 'Not supported', + 500, 'Unknown error', + 503, 'Connection failed', + 500, 'Out of memory', + 400, 'Parse error', + 500, 'System error', +]; + +function ubus_call(object, method, args) { + ubus.error(); // clear previous error + + let res = ubus.call(object, method, args); + + last_ubus_error = ubus.error(true); + + return res ?? !last_ubus_error; +} + +function ubus_state_to_http(err) { + let code = ubus_error_map[(err << 1) + 0] ?? 200; + let msg = ubus_error_map[(err << 1) + 1] ?? 'OK'; + + http.status(code, msg); + + if (code != 204) { + http.prepare_content('text/plain'); + http.write(msg); + } +} + +function uci_apply(rollback) { + if (rollback) { + const timeout = +(config?.apply?.rollback ?? 90) || 0; + const success = ubus_call('uci', 'apply', { + ubus_rpc_session: ctx.authsession, + timeout: max(timeout, 90), + rollback: true + }); + + if (success) { + const token = dispatcher.randomid(16); + + ubus.call('session', 'set', { + ubus_rpc_session: '00000000000000000000000000000000', + values: { + rollback: { + token, + session: ctx.authsession, + timeout: time() + timeout + } + } + }); + + return token; + } + + return null; + } + else { + let changes = ubus_call('uci', 'changes', { ubus_rpc_session: ctx.authsession })?.changes; + + for (let config in changes) + if (!ubus_call('uci', 'commit', { ubus_rpc_session: ctx.authsession, config })) + return false; + + return ubus_call('uci', 'apply', { + ubus_rpc_session: ctx.authsession, + rollback: false + }); + } +} + +function uci_confirm(token) { + const data = ubus.call('session', 'get', { + ubus_rpc_session: '00000000000000000000000000000000', + keys: [ 'rollback' ] + })?.values?.rollback; + + if (type(data?.token) != 'string' || type(data?.session) != 'string' || + type(data?.timeout) != 'int' || data.timeout < time()) { + last_ubus_error = STATUS_NO_DATA; + + return false; + } + + if (token != data.token) { + last_ubus_error = STATUS_PERMISSION_DENIED; + + return false; + } + + if (!ubus_call('uci', 'confirm', { ubus_rpc_session: data.session })) + return false; + + ubus_call('session', 'set', { + ubus_rpc_session: '00000000000000000000000000000000', + values: { rollback: {} } + }); + + return true; +} + + +return { + action_apply_rollback: function() { + const token = uci_apply(true); + + if (token) { + http.prepare_content('application/json; charset=UTF-8'); + http.write_json({ token }); + } + else { + ubus_state_to_http(last_ubus_error); + } + }, + + action_apply_unchecked: function() { + uci_apply(false); + ubus_state_to_http(last_ubus_error); + }, + + action_confirm: function() { + uci_confirm(http.formvalue('token')); + ubus_state_to_http(last_ubus_error); + }, + + action_revert: function() { + for (let config in ubus_call('uci', 'changes', { ubus_rpc_session: ctx.authsession })?.changes) + if (!ubus_call('uci', 'revert', { ubus_rpc_session: ctx.authsession, config })) + break; + + ubus_state_to_http(last_ubus_error); + } +}; diff --git a/modules/luci-base/ucode/dispatcher.uc b/modules/luci-base/ucode/dispatcher.uc new file mode 100644 index 0000000000..84eff71d3a --- /dev/null +++ b/modules/luci-base/ucode/dispatcher.uc @@ -0,0 +1,942 @@ +// Copyright 2022 Jo-Philipp Wich +// Licensed to the public under the Apache License 2.0. + +import { open, stat, glob, lsdir, unlink, basename } from 'fs'; +import { striptags, entityencode } from 'html'; +import { connect } from 'ubus'; +import { cursor } from 'uci'; +import { rand } from 'math'; + +import { hash, load_catalog, change_catalog, translate, ntranslate, getuid } from 'luci.core'; +import { revision as luciversion, branch as luciname } from 'luci.version'; +import { default as LuCIRuntime } from 'luci.runtime'; +import { urldecode } from 'luci.http'; + +let ubus = connect(); +let uci = cursor(); + +let indexcache = "/tmp/luci-indexcache"; + +let http, runtime, tree, luabridge; + +function error404(msg) { + http.status(404, 'Not Found'); + + try { + runtime.render('error404', { message: msg ?? 'Not found' }); + } + catch { + http.header('Content-Type', 'text/plain; charset=UTF-8'); + http.write(msg ?? 'Not found'); + } + + return false; +} + +function error500(msg, ex) { + if (!http.eoh) { + http.status(500, 'Internal Server Error'); + http.header('Content-Type', 'text/html; charset=UTF-8'); + } + + try { + runtime.render('error500', { + title: ex?.type ?? 'Runtime exception', + message: replace( + msg, + /(\s)((\/[A-Za-z0-9_.-]+)+:\d+|\[string "[^"]+"\]:\d+)/g, + '$1$2' + ), + exception: ex + }); + } + catch { + http.write('\n'); + http.write(`

${trim(ex)}

\n`); + + if (ex) { + http.write(`

${trim(ex.message)}

\n`); + http.write(`
${trim(ex.stacktrace[0].context)}
\n`); + } + } + + exit(0); +} + +function load_luabridge(optional) { + if (luabridge == null) { + try { + luabridge = require('lua'); + } + catch (ex) { + luabridge = false; + + if (!optional) + error500('No Lua runtime installed'); + } + } + + return luabridge; +} + +function determine_request_language() { + let lang = uci.get('luci', 'main', 'lang') || 'auto'; + + if (lang == 'auto') { + for (let tag in split(http.getenv('HTTP_ACCEPT_LANGUAGE'), ',')) { + tag = split(trim(split(tag, ';')?.[0]), '-'); + + if (tag) { + let cc = tag[1] ? `${tag[0]}_${lc(tag[1])}` : null; + + if (cc && uci.get('luci', 'languages', cc)) { + lang = cc; + break; + } + else if (uci.get('luci', 'languages', tag[0])) { + lang = tag[0]; + break; + } + } + } + } + + if (lang == 'auto') + lang = 'en'; + + if (load_catalog(lang, '/usr/lib/lua/luci/i18n')) + change_catalog(lang); + + return lang; +} + +function determine_version() { + let res = { luciname, luciversion }; + + for (let f = open("/etc/os-release"), l = f?.read?.("line"); l; l = f.read?.("line")) { + let kv = split(l, '=', 2); + + switch (kv[0]) { + case 'NAME': + res.distname = trim(kv[1], '"\' \n'); + break; + + case 'VERSION': + res.distversion = trim(kv[1], '"\' \n'); + break; + + case 'HOME_URL': + res.disturl = trim(kv[1], '"\' \n'); + break; + + case 'BUILD_ID': + res.distrevision = trim(kv[1], '"\' \n'); + break; + } + } + + return res; +} + +function read_jsonfile(path, defval) { + let rv; + + try { + rv = json(open(path, "r")); + } + catch (e) { + rv = defval; + } + + return rv; +} + +function read_cachefile(file, reader) { + let euid = getuid(), + fstat = stat(file), + fuid = fstat?.uid, + perm = fstat?.perm; + + if (euid != fuid || + perm?.group_read || perm?.group_write || perm?.group_exec || + perm?.other_read || perm?.other_write || perm?.other_exec) + return null; + + return reader(file); +} + +function check_fs_depends(spec) { + for (let path, kind in spec) { + if (kind == 'directory') { + if (!length(lsdir(path))) + return false; + } + else if (kind == 'executable') { + let fstat = stat(path); + + if (fstat?.type != 'file' || fstat?.user_exec == false) + return false; + } + else if (kind == 'file') { + let fstat = stat(path); + + if (fstat?.type != 'file') + return false; + } + } + + return true; +} + +function check_uci_depends_options(conf, s, opts) { + if (type(opts) == 'string') { + return (s['.type'] == opts); + } + else if (opts === true) { + for (let option, value in s) + if (ord(option) != 46) + return true; + } + else if (type(opts) == 'object') { + for (let option, value in opts) { + let sval = s[option]; + + if (type(sval) == 'array') { + if (!(value in sval)) + return false; + } + else if (value === true) { + if (sval == null) + return false; + } + else { + if (sval != value) + return false; + } + } + } + + return true; +} + +function check_uci_depends_section(conf, sect) { + for (let section, options in sect) { + let stype = match(section, /^@([A-Za-z0-9_-]+)$/); + + if (stype) { + let found = false; + + uci.load(conf); + uci.foreach(conf, stype[1], (s) => { + if (check_uci_depends_options(conf, s, options)) { + found = true; + return false; + } + }); + + if (!found) + return false; + } + else { + let s = uci.get_all(conf, section); + + if (!s || !check_uci_depends_options(conf, s, options)) + return false; + } + } + + return true; +} + +function check_uci_depends(conf) { + for (let config, values in conf) { + if (values == true) { + let found = false; + + uci.load(config); + uci.foreach(config, null, () => { found = true }); + + if (!found) + return false; + } + else if (type(values) == 'object') { + if (!check_uci_depends_section(config, values)) + return false; + } + } + + return true; +} + +function check_depends(spec) { + if (type(spec?.depends?.fs) in ['array', 'object']) { + let satisfied = false; + let alternatives = (type(spec.depends.fs) == 'array') ? spec.depends.fs : [ spec.depends.fs ]; + + for (let alternative in alternatives) { + if (check_fs_depends(alternative)) { + satisfied = true; + break; + } + } + + if (!satisfied) + return false; + } + + if (type(spec?.depends?.uci) in ['array', 'object']) { + let satisfied = false; + let alternatives = (type(spec.depends.uci) == 'array') ? spec.depends.uci : [ spec.depends.uci ]; + + for (let alternative in alternatives) { + if (check_uci_depends(alternative)) { + satisfied = true; + break; + } + } + + if (!satisfied) + return false; + } + + return true; +} + +function check_acl_depends(require_groups, groups) { + if (length(require_groups)) { + let writable = false; + + for (let group in require_groups) { + let read = ('read' in groups?.[group]); + let write = ('write' in groups?.[group]); + + if (!read && !write) + return null; + + if (write) + writable = true; + } + + return writable; + } + + return true; +} + +function hash_filelist(files) { + let hashval = 0x1b756362; + + for (let file in files) { + let st = stat(file); + + if (st) + hashval = hash(sprintf("%x|%x|%x", st.ino, st.mtime, st.size), hashval); + } + + return hashval; +} + +function build_pagetree() { + let tree = { action: { type: 'firstchild' } }; + + let schema = { + action: 'object', + auth: 'object', + cors: 'bool', + depends: 'object', + order: 'int', + setgroup: 'string', + setuser: 'string', + title: 'string', + wildcard: 'bool', + firstchild_ineligible: 'bool' + }; + + let files = glob('/usr/share/luci/menu.d/*.json', '/usr/lib/lua/luci/controller/*.lua', '/usr/lib/lua/luci/controller/*/*.lua'); + let cachefile; + + if (indexcache) { + cachefile = sprintf('%s.%08x.json', indexcache, hash_filelist(files)); + + let res = read_cachefile(cachefile, read_jsonfile); + + if (res) + return res; + + for (let path in glob(indexcache + '.*.json')) + unlink(path); + } + + for (let file in files) { + let data; + + if (substr(file, -5) == '.json') + data = read_jsonfile(file); + else if (load_luabridge(true)) + data = runtime.call('luci.dispatcher', 'process_lua_controller', file); + else + warn(`Lua controller ${file} present but no Lua runtime installed.\n`); + + if (type(data) == 'object') { + for (let path, spec in data) { + if (type(spec) == 'object') { + let node = tree; + + for (let s in match(path, /[^\/]+/g)) { + if (s[0] == '*') { + node.wildcard = true; + break; + } + + node.children ??= {}; + node.children[s[0]] ??= {}; + node = node.children[s[0]]; + } + + if (node !== tree) { + for (let k, t in schema) + if (type(spec[k]) == t) + node[k] = spec[k]; + + node.satisfied = check_depends(spec); + } + } + } + } + } + + if (cachefile) { + let fd = open(cachefile, 'w', 0600); + + if (fd) { + fd.write(tree); + fd.close(); + } + } + + return tree; +} + +function menu_json(acl) { + tree ??= build_pagetree(); + + return tree; +} + +function ctx_append(ctx, name, node) { + ctx.path ??= []; + push(ctx.path, name); + + ctx.acls ??= []; + push(ctx.acls, ...(node?.depends?.acl || [])); + + ctx.auth = node.auth || ctx.auth; + ctx.cors = node.cors || ctx.cors; + ctx.suid = node.setuser || ctx.suid; + ctx.sgid = node.setgroup || ctx.sgid; + + return ctx; +} + +function session_retrieve(sid, allowed_users) { + let sdat = ubus.call("session", "get", { ubus_rpc_session: sid }); + let sacl = ubus.call("session", "access", { ubus_rpc_session: sid }); + + if (type(sdat?.values?.token) == 'string' && + (!length(allowed_users) || sdat?.values?.username in allowed_users)) { + // uci:set_session_id(sid) + return { + sid, + data: sdat.values, + acls: length(sacl) ? sacl : {} + }; + } + + return null; +} + +function randomid(num_bytes) { + let bytes = []; + + while (num_bytes-- > 0) + push(bytes, sprintf('%02x', rand() % 256)); + + return join('', bytes); +} + +function syslog(prio, msg) { + warn(sprintf("[%s] %s\n", prio, msg)); +} + +function session_setup(user, pass, path) { + let timeout = uci.get('luci', 'sauth', 'sessiontime'); + let login = ubus.call("session", "login", { + username: user, + password: pass, + timeout: timeout ? +timeout : null + }); + + if (type(login?.ubus_rpc_session) == 'string') { + ubus.call("session", "set", { + ubus_rpc_session: login.ubus_rpc_session, + values: { token: randomid(16) } + }); + syslog("info", sprintf("luci: accepted login on /%s for %s from %s", + join('/', path), user || "?", http.getenv("REMOTE_ADDR") || "?")); + + return session_retrieve(login.ubus_rpc_session); + } + + syslog("info", sprintf("luci: failed login on /%s for %s from %s", + join('/', path), user || "?", http.getenv("REMOTE_ADDR") || "?")); +} + +function check_authentication(method) { + let m = match(method, /^([[:alpha:]]+):(.+)$/); + let sid; + + switch (m?.[1]) { + case 'cookie': + sid = http.getcookie(m[2]); + break; + + case 'param': + sid = http.formvalue(m[2]); + break; + + case 'query': + sid = http.formvalue(m[2], true); + break; + } + + return sid ? session_retrieve(sid) : null; +} + +function is_authenticated(auth) { + for (let method in auth?.methods) { + let session = check_authentication(method); + + if (session) + return session; + } + + return null; +} + +function node_weight(node) { + let weight = min(node.order ?? 9999, 9999); + + if (node.auth?.login) + weight += 10000; + + return weight; +} + +function clone(src) { + switch (type(src)) { + case 'array': + return map(src, clone); + + case 'object': + let dest = {}; + + for (let k, v in src) + dest[k] = clone(v); + + return dest; + + default: + return src; + } +} + +function resolve_firstchild(node, session, login_allowed, ctx) { + let candidate, candidate_ctx; + + for (let name, child in node.children) { + if (!child.satisfied) + continue; + + if (!session) + session = is_authenticated(node.auth); + + let cacl = child.depends?.acl; + let login = login_allowed || child.auth?.login; + + if (login || check_acl_depends(cacl, session?.acls?.["access-group"]) != null) { + if (child.title && type(child.action) == "object") { + let child_ctx = ctx_append(clone(ctx), name, child); + if (child.action.type == "firstchild") { + if (!candidate || node_weight(candidate) > node_weight(child)) { + let have_grandchild = resolve_firstchild(child, session, login, child_ctx); + if (have_grandchild) { + candidate = child; + candidate_ctx = child_ctx; + } + } + } + else if (!child.firstchild_ineligible) { + if (!candidate || node_weight(candidate) > node_weight(child)) { + candidate = child; + candidate_ctx = child_ctx; + } + } + } + } + } + + if (!candidate) + return false; + + for (let k, v in candidate_ctx) + ctx[k] = v; + + return true; +} + +function resolve_page(tree, request_path) { + let node = tree; + let login = false; + let session = null; + let ctx = {}; + + for (let i, s in request_path) { + node = node.children?.[s]; + + if (!node?.satisfied) + break; + + ctx_append(ctx, s, node); + + if (!session) + session = is_authenticated(node.auth); + + if (!login && node.auth?.login) + login = true; + + if (node.wildcard) { + ctx.request_args = []; + ctx.request_path = ctx.path ? [ ...ctx.path ] : []; + + while (++i < length(request_path)) { + push(ctx.request_path, request_path[i]); + push(ctx.request_args, request_path[i]); + } + + break; + } + } + + if (node?.action?.type == 'firstchild') + resolve_firstchild(node, session, login, ctx); + + ctx.acls ??= {}; + ctx.path ??= []; + ctx.request_args ??= []; + ctx.request_path ??= request_path ? [ ...request_path ] : []; + + ctx.authsession = session?.sid; + ctx.authtoken = session?.data?.token; + ctx.authuser = session?.data?.username; + ctx.authacl = session?.acls; + + node = tree; + + for (let s in ctx.path) { + node = node.children[s]; + assert(node, "Internal node resolve error"); + } + + return { node, ctx, session }; +} + +function require_post_security(target, args) { + if (target?.type == 'arcombine') + return require_post_security(length(args) ? target?.targets?.[1] : target?.targets?.[0], args); + + if (type(target?.post) == 'object') { + for (let param_name, required_val in target.post) { + let request_val = http.formvalue(param_name); + + if ((type(required_val) == 'string' && request_val != required_val) || + (required_val == true && request_val == null)) + return false; + } + + return true; + } + + return (target?.post == true); +} + +function test_post_security(authtoken) { + if (http.getenv("REQUEST_METHOD") != "POST") { + http.status(405, "Method Not Allowed"); + http.header("Allow", "POST"); + + return false; + } + + if (http.formvalue("token") != authtoken) { + http.status(403, "Forbidden"); + runtime.render("csrftoken"); + + return false; + } + + return true; +} + +function build_url(...path) { + let url = [ http.getenv('SCRIPT_NAME') ?? '' ]; + + for (let p in path) + if (match(p, /^[A-Za-z0-9_%.\/,;-]+$/)) + push(url, '/', p); + + if (length(url) == 1) + push(url, '/'); + + return join('', url); +} + +function lookup(...segments) { + let node = menu_json(); + let path = []; + + for (let segment in segments) + for (let name in split(segment, '/')) + push(path, name); + + for (let name in path) { + node = node.children[name]; + + if (!node) + return null; + + if (node.leaf) + break; + } + + return { node, url: build_url(...path) }; +} + +function rollback_pending() { + const now = time(); + const rv = ubus.call('session', 'get', { + ubus_rpc_session: '00000000000000000000000000000000', + keys: [ 'rollback' ] + }); + + if (type(rv?.values?.rollback?.token) != 'string' || + type(rv?.values?.rollback?.session) != 'string' || + type(rv?.values?.rollback?.timeout) != 'int' || + rv.values.rollback.timeout <= now) + return false; + + return { + remaining: rv.values.rollback.timeout - now, + session: rv.values.rollback.session, + token: rv.values.rollback.token + }; +} + +let dispatch; + +function run_action(request_path, lang, tree, resolved, action) { + switch (action?.type) { + case 'template': + runtime.render(action.path, {}); + break; + + case 'view': + runtime.render('view', { view: action.path }); + break; + + case 'call': + http.write(render(() => { + runtime.call(action.module, action.function, + ...(action.parameters ?? []), + ...resolved.ctx.request_args + ); + })); + break; + + case 'function': + const mod = require(action.module); + + assert(type(mod[action.function]) == 'function', + `Module '${action.module}' does not export function '${action.function}'`); + + http.write(render(() => { + call(mod[action.function], mod, runtime.env, + ...(action.parameters ?? []), + ...resolved.ctx.request_args + ); + })); + break; + + case 'alias': + dispatch(http, [ ...split(action.path, '/'), ...resolved.ctx.request_args ]); + break; + + case 'rewrite': + dispatch(http, [ + ...splice([ ...request_path ], 0, action.remove), + ...split(action.path, '/'), + ...resolved.ctx.request_args + ]); + break; + + case 'firstchild': + if (!length(tree.children)) + error404("No root node was registered, this usually happens if no module was installed.\n" + + "Install luci-mod-admin-full and retry. " + + "If the module is already installed, try removing the /tmp/luci-indexcache file."); + else + error404(`No page is registered at '/${join("/", resolved.ctx.request_path)}'.\n` + + "If this url belongs to an extension, make sure it is properly installed.\n" + + "If the extension was recently installed, try removing the /tmp/luci-indexcache file."); + break; + + default: + error500(`Unhandled action type ${action?.type ?? '?'}`); + } +} + +dispatch = function(_http, path) { + http = _http; + + let version = determine_version(); + let lang = determine_request_language(); + + runtime = LuCIRuntime({ + http, + ubus, + uci, + ctx: {}, + version, + config: { + main: uci.get_all('luci', 'main') ?? {}, + apply: uci.get_all('luci', 'apply') ?? {} + }, + dispatcher: { + rollback_pending, + is_authenticated, + load_luabridge, + lookup, + menu_json, + build_url, + randomid, + error404, + error500, + lang + }, + striptags, + entityencode, + _: (...args) => translate(...args) ?? args[0], + N_: (...args) => ntranslate(...args) ?? (n[0] == 1 ? n[1] : n[2]), + }); + + try { + let menu = menu_json(); + + path ??= map(match(http.getenv('PATH_INFO'), /[^\/]+/g), m => m[0]); + + let resolved = resolve_page(menu, path); + + runtime.env.ctx = resolved.ctx; + runtime.env.node = resolved.node; + + if (length(resolved.ctx.auth)) { + let session = is_authenticated(resolved.ctx.auth); + + if (!session && resolved.ctx.auth.login) { + let user = http.getenv('HTTP_AUTH_USER'); + let pass = http.getenv('HTTP_AUTH_PASS'); + + if (user == null && pass == null) { + user = http.formvalue('luci_username'); + pass = http.formvalue('luci_password'); + } + + if (user != null && pass != null) + session = session_setup(user, pass, resolved.ctx.request_path); + + if (!session) { + resolved.ctx.path = []; + + http.status(403, 'Forbidden'); + http.header('X-LuCI-Login-Required', 'yes'); + + let scope = { duser: 'root', fuser: user }; + + try { + runtime.render(`themes/${basename(runtime.env.media)}/sysauth`, scope); + } + catch (e) { + runtime.render('sysauth', scope); + } + + return; + } + + let cookie_name = (http.getenv('HTTPS') == 'on') ? 'sysauth_https' : 'sysauth_http', + cookie_secure = (http.getenv('HTTPS') == 'on') ? '; secure' : ''; + + http.header('Set-Cookie', `${cookie_name}=${session.sid}; path=${build_url()}; SameSite=strict; HttpOnly${cookie_secure}`); + http.redirect(build_url(...resolved.ctx.request_path)); + + return; + } + + if (!session) { + http.status(403, 'Forbidden'); + http.header('X-LuCI-Login-Required', 'yes'); + + return; + } + + resolved.ctx.authsession ??= session.sid; + resolved.ctx.authtoken ??= session.data?.token; + resolved.ctx.authuser ??= session.data?.username; + resolved.ctx.authacl ??= session.acls; + } + + if (length(resolved.ctx.acls)) { + let perm = check_acl_depends(resolved.ctx.acls, resolved.ctx.authacl?.['access-group']); + + if (perm == null) { + http.status(403, 'Forbidden'); + + return; + } + + if (resolved.node) + resolved.node.readonly = !perm; + } + + let action = resolved.node.action; + + if (action?.type == 'arcombine') + action = length(resolved.ctx.request_args) ? action.targets?.[1] : action.targets?.[0]; + + if (resolved.ctx.cors && http.getenv('REQUEST_METHOD') == 'OPTIONS') { + http.status(200, 'OK'); + http.header('Access-Control-Allow-Origin', http.getenv('HTTP_ORIGIN') ?? '*'); + http.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + + return; + } + + if (require_post_security(action) && !test_post_security(resolved.ctx.authtoken)) + return; + + run_action(path, lang, menu, resolved, action); + } + catch (ex) { + error500('Unhandled exception during request dispatching', ex); + } +}; + +export default dispatch; diff --git a/modules/luci-base/ucode/http.uc b/modules/luci-base/ucode/http.uc new file mode 100644 index 0000000000..b464497eac --- /dev/null +++ b/modules/luci-base/ucode/http.uc @@ -0,0 +1,574 @@ +// Copyright 2022 Jo-Philipp Wich +// Licensed to the public under the Apache License 2.0. + +import { + urlencode as _urlencode, + urldecode as _urldecode, + urlencoded_parser, multipart_parser, header_attribute, + ENCODE_IF_NEEDED, ENCODE_FULL, DECODE_IF_NEEDED, DECODE_PLUS +} from 'lucihttp'; + +import { + error as fserror, + stdin, stdout, mkstemp +} from 'fs'; + +// luci.http module scope +export let HTTP_MAX_CONTENT = 1024*100; // 100 kB maximum content size + +// Decode a mime encoded http message body with multipart/form-data +// Content-Type. Stores all extracted data associated with its parameter name +// in the params table within the given message object. Multiple parameter +// values are stored as tables, ordinary ones as strings. +// If an optional file callback function is given then it is fed with the +// file contents chunk by chunk and only the extracted file name is stored +// within the params table. The callback function will be called subsequently +// with three arguments: +// o Table containing decoded (name, file) and raw (headers) mime header data +// o String value containing a chunk of the file data +// o Boolean which indicates whether the current chunk is the last one (eof) +export function mimedecode_message_body(src, msg, file_cb) { + let len = 0, maxlen = +msg.env.CONTENT_LENGTH; + let header, field, parser; + + parser = multipart_parser(msg.env.CONTENT_TYPE, function(what, buffer, length) { + if (what == parser.PART_INIT) { + field = {}; + } + else if (what == parser.HEADER_NAME) { + header = lc(buffer); + } + else if (what == parser.HEADER_VALUE && header) { + if (lc(header) == 'content-disposition' && + header_attribute(buffer, null) == 'form-data') { + field.name = header_attribute(buffer, 'name'); + field.file = header_attribute(buffer, 'filename'); + field[1] = field.file; + } + + field.headers = field.headers || {}; + field.headers[header] = buffer; + } + else if (what == parser.PART_BEGIN) { + return !field.file; + } + else if (what == parser.PART_DATA && field.name && length > 0) { + if (field.file) { + if (file_cb) { + file_cb(field, buffer, false); + + msg.params[field.name] = msg.params[field.name] || field; + } + else { + if (!field.fd) + field.fd = mkstemp(field.name); + + if (field.fd) { + field.fd.write(buffer); + msg.params[field.name] = msg.params[field.name] || field; + } + } + } + else { + field.value = buffer; + } + } + else if (what == parser.PART_END && field.name) { + if (field.file && msg.params[field.name]) { + if (file_cb) + file_cb(field, '', true); + else if (field.fd) + field.fd.seek(0); + } + else { + let val = msg.params[field.name]; + + if (type(val) == 'array') + push(val, field.value || ''); + else if (val != null) + msg.params[field.name] = [ val, field.value || '' ]; + else + msg.params[field.name] = field.value || ''; + } + + field = null; + } + else if (what == parser.ERROR) { + err = buffer; + } + + return true; + }, HTTP_MAX_CONTENT); + + while (true) { + let chunk = src(); + + len += length(chunk); + + if (maxlen && len > maxlen + 2) + die('Message body size exceeds Content-Length'); + + if (!parser.parse(chunk)) + die(err); + + if (chunk == null) + break; + } +}; + +// Decode an urlencoded http message body with application/x-www-urlencoded +// Content-Type. Stores all extracted data associated with its parameter name +// in the params table within the given message object. Multiple parameter +// values are stored as tables, ordinary ones as strings. +export function urldecode_message_body(src, msg) { + let len = 0, maxlen = +msg.env.CONTENT_LENGTH; + let err, name, value, parser; + + parser = urlencoded_parser(function (what, buffer, length) { + if (what == parser.TUPLE) { + name = null; + value = null; + } + else if (what == parser.NAME) { + name = _urldecode(buffer, DECODE_PLUS); + } + else if (what == parser.VALUE && name) { + let val = msg.params[name]; + + if (type(val) == 'array') + push(val, _urldecode(buffer, DECODE_PLUS) || ''); + else if (val != null) + msg.params[name] = [ val, _urldecode(buffer, DECODE_PLUS) || '' ]; + else + msg.params[name] = _urldecode(buffer, DECODE_PLUS) || ''; + } + else if (what == parser.ERROR) { + err = buffer; + } + + return true; + }, HTTP_MAX_CONTENT); + + while (true) { + let chunk = src(); + + len += length(chunk); + + if (maxlen && len > maxlen + 2) + die('Message body size exceeds Content-Length'); + + if (!parser.parse(chunk)) + die(err); + + if (chunk == null) + break; + } +}; + +// This function will examine the Content-Type within the given message object +// to select the appropriate content decoder. +// Currently the application/x-www-urlencoded and application/form-data +// mime types are supported. If the encountered content encoding can't be +// handled then the whole message body will be stored unaltered as 'content' +// property within the given message object. +export function parse_message_body(src, msg, filecb) { + if (msg.env.CONTENT_LENGTH || msg.env.REQUEST_METHOD == 'POST') { + let ctype = header_attribute(msg.env.CONTENT_TYPE, null); + + // Is it multipart/mime ? + if (ctype == 'multipart/form-data') + return mimedecode_message_body(src, msg, filecb); + + // Is it application/x-www-form-urlencoded ? + else if (ctype == 'application/x-www-form-urlencoded') + return urldecode_message_body(src, msg); + + // Unhandled encoding + // If a file callback is given then feed it chunk by chunk, else + // store whole buffer in message.content + let sink; + + // If we have a file callback then feed it + if (type(filecb) == 'function') { + let meta = { + name: 'raw', + encoding: msg.env.CONTENT_TYPE + }; + + sink = (chunk) => { + if (chunk != null) + return filecb(meta, chunk, false); + else + return filecb(meta, null, true); + }; + } + + // ... else append to .content + else { + let chunks = [], len = 0; + + sink = (chunk) => { + len += length(chunk); + + if (len > HTTP_MAX_CONTENT) + die('POST data exceeds maximum allowed length'); + + if (chunk != null) { + push(chunks, chunk); + } + else { + msg.content = join('', chunks); + msg.content_length = len; + } + }; + } + + // Pump data... + while (true) { + let chunk = src(); + + sink(chunk); + + if (chunk == null) + break; + } + + return true; + } + + return false; +}; + +export function build_querystring(q) { + let s = []; + + for (let k, v in q) { + push(s, + length(s) ? '&' : '?', + _urlencode(k, ENCODE_IF_NEEDED | ENCODE_FULL) || k, + '=', + _urlencode(v, ENCODE_IF_NEEDED | ENCODE_FULL) || v + ); + } + + return join('', s); +}; + +export function urlencode(value) { + if (value == null) + return null; + + value = '' + value; + + return _urlencode(value, ENCODE_IF_NEEDED | ENCODE_FULL) || value; +}; + +export function urldecode(value, decode_plus) { + if (value == null) + return null; + + value = '' + value; + + return _urldecode(value, DECODE_IF_NEEDED | (decode_plus ? DECODE_PLUS : 0)) || value; +}; + +// Extract and split urlencoded data pairs, separated bei either "&" or ";" +// from given url or string. Returns a table with urldecoded values. +// Simple parameters are stored as string values associated with the parameter +// name within the table. Parameters with multiple values are stored as array +// containing the corresponding values. +export function urldecode_params(url, tbl) { + let parser, name, value; + let params = tbl || {}; + + parser = urlencoded_parser(function(what, buffer, length) { + if (what == parser.TUPLE) { + name = null; + value = null; + } + else if (what == parser.NAME) { + name = _urldecode(buffer); + } + else if (what == parser.VALUE && name) { + params[name] = _urldecode(buffer) || ''; + } + + return true; + }); + + if (parser) { + let m = match(('' + (url || '')), /[^?]*$/); + + parser.parse(m ? m[0] : ''); + parser.parse(null); + } + + return params; +}; + +// Encode each key-value-pair in given table to x-www-urlencoded format, +// separated by '&'. Tables are encoded as parameters with multiple values by +// repeating the parameter name with each value. +export function urlencode_params(tbl) { + let enc = []; + + for (let k, v in tbl) { + if (type(v) == 'array') { + for (let v2 in v) { + if (length(enc)) + push(enc, '&'); + + push(enc, + _urlencode(k), + '=', + _urlencode('' + v2)); + } + } + else { + if (length(enc)) + push(enc, '&'); + + push(enc, + _urlencode(k), + '=', + _urlencode('' + v)); + } + } + + return join(enc, ''); +}; + + +// Default IO routines suitable for CGI invocation +let avail_len = +getenv('CONTENT_LENGTH'); + +const default_source = () => { + let rlen = min(avail_len, 4096); + + if (rlen == 0) { + stdin.close(); + + return null; + } + + let chunk = stdin.read(rlen); + + if (chunk == null) + die(`Input read error: ${fserror()}`); + + avail_len -= length(chunk); + + return chunk; +}; + +const default_sink = (...chunks) => { + for (let chunk in chunks) + stdout.write(chunk); + + stdout.flush(); +}; + +const Class = { + formvalue: function(name, noparse) { + if (!noparse && !this.parsed_input) + this._parse_input(); + + if (name != null) + return this.message.params[name]; + else + return this.message.params; + }, + + formvaluetable: function(prefix) { + let vals = {}; + + prefix = (prefix || '') + '.'; + + if (!this.parsed_input) + this._parse_input(); + + for (let k, v in this.message.params) + if (index(k, prefix) == 0) + vals[substr(k, length(prefix))] = '' + v; + + return vals; + }, + + content: function() { + if (!this.parsed_input) + this._parse_input(); + + return this.message.content; + }, + + getcookie: function(name) { + return header_attribute(`cookie; ${this.getenv('HTTP_COOKIE') ?? ''}`, name); + }, + + getenv: function(name) { + if (name != null) + return this.message.env[name]; + else + return this.message.env; + }, + + setfilehandler: function(callback) { + if (type(callback) == 'resource' && type(callback.call) == 'function') + this.filehandler = (...args) => callback.call(...args); + else if (type(callback) == 'function') + this.filehandler = callback; + else + die('Invalid callback argument for setfilehandler()'); + + if (!this.parsed_input) + return; + + // If input has already been parsed then uploads are stored as unlinked + // temporary files pointed to by open file handles in the parameter + // value table. Loop all params, and invoke the file callback for any + // param with an open file handle. + for (let name, value in this.message.params) { + while (value?.fd) { + let data = value.fd.read(1024); + let eof = (data == null || data == ''); + + callback(value, data, eof); + + if (eof) { + value.fd.close(); + value.fd = null; + } + } + } + }, + + _parse_input: function() { + parse_message_body( + this.input, + this.message, + this.filehandler + ); + + this.parsed_input = true; + }, + + close: function() { + this.write_headers(); + this.closed = true; + }, + + header: function(key, value) { + this.headers ??= {}; + this.headers[lc(key)] = value; + }, + + prepare_content: function(mime) { + if (!this.headers?.['content-type']) { + if (mime == 'application/xhtml+xml') { + if (index(this.getenv('HTTP_ACCEPT'), mime) == -1) { + mime = 'text/html; charset=UTF-8'; + this.header('Vary', 'Accept'); + } + } + + this.header('Content-Type', mime); + } + }, + + status: function(code, message) { + this.status_code = code ?? 200; + this.status_message = message ?? 'OK'; + }, + + write_headers: function() { + if (this.eoh) + return; + + if (!this.status_code) + this.status(); + + if (!this.headers?.['content-type']) + this.header('Content-Type', 'text/html; charset=UTF-8'); + + if (!this.headers?.['cache-control']) { + this.header('Cache-Control', 'no-cache'); + this.header('Expires', '0'); + } + + if (!this.headers?.['x-frame-options']) + this.header('X-Frame-Options', 'SAMEORIGIN'); + + if (!this.headers?.['x-xss-protection']) + this.header('X-XSS-Protection', '1; mode=block'); + + if (!this.headers?.['x-content-type-options']) + this.header('X-Content-Type-Options', 'nosniff'); + + this.output('Status: '); + this.output(this.status_code); + this.output(' '); + this.output(this.status_message); + this.output('\r\n'); + + for (let k, v in this.headers) { + this.output(k); + this.output(': '); + this.output(v); + this.output('\r\n'); + } + + this.output('\r\n'); + + this.eoh = true; + }, + + // If the content chunk is nil this function will automatically invoke close. + write: function(content) { + if (content != null) { + this.write_headers(); + this.output(content); + + return true; + } + else { + this.close(); + } + }, + + redirect: function(url) { + this.status(302, 'Found'); + this.header('Location', url ?? '/'); + this.close(); + }, + + write_json: function(value) { + this.write(sprintf('%.J', value)); + }, + + urlencode, + urlencode_params, + + urldecode, + urldecode_params, + + build_querystring +}; + +export default function(env, sourcein, sinkout) { + return proto({ + input: sourcein ?? default_source, + output: sinkout ?? default_sink, + + // File handler nil by default to let .content() work + file: null, + + // HTTP-Message table + message: { + env, + headers: {}, + params: urldecode_params(env?.QUERY_STRING ?? '') + }, + + parsed_input: false + }, Class); +}; diff --git a/modules/luci-base/ucode/runtime.uc b/modules/luci-base/ucode/runtime.uc new file mode 100644 index 0000000000..a8b6812e74 --- /dev/null +++ b/modules/luci-base/ucode/runtime.uc @@ -0,0 +1,162 @@ +// Copyright 2022 Jo-Philipp Wich +// Licensed to the public under the Apache License 2.0. + +import { access, basename } from 'fs'; +import { cursor } from 'uci'; + +const template_directory = '/usr/share/ucode/luci/template'; + +function cut_message(msg) { + return trim(replace(msg, /\n--\n.*$/, '')); +} + +function format_nested_exception(ex) { + let msg = replace(cut_message(ex.message), /(\n+( \|[^\n]*(\n|$))+)/, (m, m1) => { + m1 = replace(m1, /(^|\n) \| ?/g, '$1'); + m = match(m1, /^(.+?)\n(In.*line \d+, byte \d+:.+)$/); + + return ` +
+
${cut_message(m ? m[1] : m1)}
+ ${m ? `
${trim(m[2])}
` : ''} +
+ `; + }); + + return ` +
+
${cut_message(msg)}
+
${trim(ex.stacktrace[0].context)}
+
+ `; +} + +function format_lua_exception(ex) { + let m = match(ex.message, /^(.+)\nstack traceback:\n(.+)$/); + + return ` +
+
${cut_message(m ? m[1] : ex.message)}
+
${m ? trim(replace(m[2], /(^|\n)\t/g, '$1')) : ex.stacktrace[0].context}
+
+ `; +} + +const Class = { + init_lua: function() { + if (!this.L) { + this.L = this.env.dispatcher.load_luabridge().create(); + this.L.set('L', proto({ write: print }, this.env)); + this.L.invoke('require', 'luci.ucodebridge'); + + this.env.lua_active = true; + } + + return this.L; + }, + + render_ucode: function(path, scope) { + let tmplfunc = loadfile(path, { raw_mode: false }); + call(tmplfunc, null, scope ?? {}); + }, + + render_lua: function(path, scope) { + let vm = this.init_lua(); + let render = vm.get('_G', 'luci', 'ucodebridge', 'render'); + + render.call(path, scope ?? {}); + }, + + trycompile: function(path) { + let ucode_path = `${template_directory}/${path}.ut`; + + if (access(ucode_path)) { + try { + loadfile(ucode_path, { raw_mode: false }); + } + catch (ucode_err) { + return `Unable to compile '${path}' as ucode template: ${format_nested_exception(ucode_err)}`; + } + } + else { + try { + let vm = this.init_lua(); + let compile = vm.get('_G', 'luci', 'ucodebridge', 'compile'); + + compile.call(path); + } + catch (lua_err) { + return `Unable to compile '${path}' as Lua template: ${format_lua_exception(lua_err)}`; + } + } + + return true; + }, + + render_any: function(path, scope) { + let ucode_path = `${template_directory}/${path}.ut`; + + scope = proto(scope ?? {}, this.scopes[-1]); + + push(this.scopes, scope); + + try { + if (access(ucode_path)) + this.render_ucode(ucode_path, scope); + else + this.render_lua(path, scope); + } + catch (ex) { + pop(this.scopes); + die(ex); + } + + pop(this.scopes); + }, + + render: function(path, scope) { + let self = this; + this.env.http.write(render(() => self.render_any(path, scope))); + }, + + call: function(modname, method, ...args) { + let vm = this.init_lua(); + let lcall = vm.get('_G', 'luci', 'ucodebridge', 'call'); + + return lcall.call(modname, method, ...args); + } +}; + +export default function(env) { + const self = proto({ env: env ??= {}, scopes: [ proto(env, global) ], global }, Class); + const uci = cursor(); + + // determine theme + let media = uci.get('luci', 'main', 'mediaurlbase'); + let status = self.trycompile(`themes/${basename(media)}/header`); + + if (status !== true) { + media = null; + + for (let k, v in uci.get_all('luci', 'themes')) { + if (substr(k, 0, 1) != '.') { + status = self.trycompile(`themes/${basename(v)}/header`); + + if (status === true) { + media = v; + break; + } + } + } + + if (!media) + error500(`Unable to render any theme header template, last error was:\n${status}`); + } + + self.env.media = media; + self.env.theme = basename(media); + self.env.resource = uci.get('luci', 'main', 'resourcebase'); + self.env.include = (...args) => self.render_any(...args); + + return self; +}; diff --git a/modules/luci-base/ucode/sys.uc b/modules/luci-base/ucode/sys.uc new file mode 100644 index 0000000000..d4db91a9b9 --- /dev/null +++ b/modules/luci-base/ucode/sys.uc @@ -0,0 +1,157 @@ +// Copyright 2022 Jo-Philipp Wich +// Licensed to the public under the Apache License 2.0. + +import { basename, readlink, readfile, open, popen, stat, glob } from 'fs'; + +export function process_list() { + const top = popen('/bin/busybox top -bn1'); + let line, list = []; + + for (let line = top.read('line'); length(line); line = top.read('line')) { + let m = match(trim(line), /^([0-9]+) +([0-9]+) +(.+) +([RSDZTWI][ { + const s = stat(path); + + return s?.type == 'file' && s?.perm?.user_exec; + }), basename); +}; + +export function init_index(name) { + const src = readfile(`/etc/init.d/${basename(name)}`, 1024); + const idx = []; + + for (let m in match(src, /^[[:space:]]*(START|STOP)=('[0-9][0-9]'|"[0-9][0-9]"|[0-9][0-9])[[:space:]]*$/gs)) { + switch (m[1]) { + case 'START': idx[0] = +trim(m[2], '"\''); break; + case 'STOP': idx[1] = +trim(m[2], '"\''); break; + } + } + + return length(idx) ? idx : null; +}; + +export function init_enabled(name) { + for (let path in glob(`/etc/rc.d/[SK][0-9][0-9]${basename(name)}`)) { + const ln = readlink(path); + const s1 = stat(index(ln, '/') == 0 ? ln : `/etc/rc.d/${ln}`); + const s2 = stat(`/etc/init.d/${basename(name)}`); + + if (s1?.inode == s2?.inode && s1?.type == 'file' && s1?.perm?.user_exec) + return true; + } + + return false; +}; + +export function init_action(name, action) { + const s = stat(`/etc/init.d/${basename(name)}`); + + if (s?.type != 'file' || s?.user_exec == false) + return false; + + return system(`env -i /etc/init.d/${basename(name)} ${action} >/dev/null`); +}; diff --git a/modules/luci-base/ucode/template/csrftoken.ut b/modules/luci-base/ucode/template/csrftoken.ut new file mode 100644 index 0000000000..4e96eebe90 --- /dev/null +++ b/modules/luci-base/ucode/template/csrftoken.ut @@ -0,0 +1,24 @@ +{# + Copyright 2015-2022 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-#} + +{% include('header') %} + +

{{ _('Form token mismatch') }}

+
+ +

{{ _('The submitted security token is invalid or already expired!') }}

+ +

{{ _(` + In order to prevent unauthorized access to the system, your request has + been blocked. Click "Continue »" below to return to the previous page. +`) }}

+ +
+ +

+ Continue » +

+ +{% include('footer') %} diff --git a/modules/luci-base/ucode/template/error404.ut b/modules/luci-base/ucode/template/error404.ut new file mode 100644 index 0000000000..90c3d3784b --- /dev/null +++ b/modules/luci-base/ucode/template/error404.ut @@ -0,0 +1,14 @@ +{# + Copyright 2008 Steven Barth + Copyright 2008-2022 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-#} + +{% include('header') %} + +

404 {{ _('Not Found') }}

+

{{ _('Sorry, the object you requested was not found.') }}

+

{{ message }}

+{{ _('Unable to dispatch') }}: {{ dispatcher.build_url(...ctx.request_path) }} + +{% include('footer') %} diff --git a/modules/luci-base/ucode/template/error500.ut b/modules/luci-base/ucode/template/error500.ut new file mode 100644 index 0000000000..39a0eec678 --- /dev/null +++ b/modules/luci-base/ucode/template/error500.ut @@ -0,0 +1,67 @@ +{# + Copyright 2022 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-#} + + + + +
+

{{ title }}

+
{{ message }}
+ + {% if (exception): %} +
+
{{ exception.message }}
+
{{ exception.stacktrace[0].context }}
+
+ {% endif %} +
diff --git a/modules/luci-base/ucode/template/footer.ut b/modules/luci-base/ucode/template/footer.ut new file mode 100644 index 0000000000..22d4f136f0 --- /dev/null +++ b/modules/luci-base/ucode/template/footer.ut @@ -0,0 +1,23 @@ +{# + Copyright 2022 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-#} + +{% const rollback = dispatcher.rollback_pending() %} +{% if (rollback || trigger_apply || trigger_revert): %} + +{% endif %} + +{% include(`themes/${theme}/footer`) %} + + diff --git a/modules/luci-base/ucode/template/header.ut b/modules/luci-base/ucode/template/header.ut new file mode 100644 index 0000000000..fb61da5146 --- /dev/null +++ b/modules/luci-base/ucode/template/header.ut @@ -0,0 +1,32 @@ +{# + Copyright 2022 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-#} + +{% + include(`themes/${theme}/header`); +-%} + + + + diff --git a/modules/luci-base/ucode/template/sysauth.ut b/modules/luci-base/ucode/template/sysauth.ut new file mode 100644 index 0000000000..0fe873d440 --- /dev/null +++ b/modules/luci-base/ucode/template/sysauth.ut @@ -0,0 +1,74 @@ +{# + Copyright 2008 Steven Barth + Copyright 2008-2012 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-#} + +{% include('header') %} + +
+ {% if (fuser): %} +
+

{{ _('Invalid username and/or password! Please try again.') }}

+
+ {% endif %} + +
+

{{ _('Authorization Required') }}

+
+ {{ _('Please enter your username and password.') }} +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+ + +
+
+ +{% + let https_ports = uci.get('uhttpd', 'main', 'listen_https') ?? []; + + https_ports = uniq(filter( + map( + (type(https_ports) == 'string') ? split(https_port, /\s+/) : https_ports, + e => +match(e, /\d+$/)?.[0] + ), + p => (p >= 0 && p <= 65535) + )); +%} + + + +{% include('footer') %} diff --git a/modules/luci-base/ucode/template/view.ut b/modules/luci-base/ucode/template/view.ut new file mode 100644 index 0000000000..11ac824290 --- /dev/null +++ b/modules/luci-base/ucode/template/view.ut @@ -0,0 +1,12 @@ +{% include('header') %} + +
+
{{ _('Loading view…') }}
+ +
+ +{% include('footer') %} diff --git a/modules/luci-base/ucode/uhttpd.uc b/modules/luci-base/ucode/uhttpd.uc new file mode 100644 index 0000000000..df1ecc7865 --- /dev/null +++ b/modules/luci-base/ucode/uhttpd.uc @@ -0,0 +1,12 @@ +{% + +import dispatch from 'luci.dispatcher'; +import request from 'luci.http'; + +global.handle_request = function(env) { + let req = request(env, uhttpd.recv, uhttpd.send); + + dispatch(req); + + req.close(); +}; diff --git a/modules/luci-base/ucode/zoneinfo.uc b/modules/luci-base/ucode/zoneinfo.uc new file mode 100644 index 0000000000..c5e588dd6a --- /dev/null +++ b/modules/luci-base/ucode/zoneinfo.uc @@ -0,0 +1,453 @@ +// Autogenerated by zoneinfo2ucode.pl + +export default { + 'Africa/Abidjan': 'GMT0', + 'Africa/Accra': 'GMT0', + 'Africa/Addis Ababa': 'EAT-3', + 'Africa/Algiers': 'CET-1', + 'Africa/Asmara': 'EAT-3', + 'Africa/Bamako': 'GMT0', + 'Africa/Bangui': 'WAT-1', + 'Africa/Banjul': 'GMT0', + 'Africa/Bissau': 'GMT0', + 'Africa/Blantyre': 'CAT-2', + 'Africa/Brazzaville': 'WAT-1', + 'Africa/Bujumbura': 'CAT-2', + 'Africa/Cairo': 'EET-2', + 'Africa/Casablanca': '<+01>-1', + 'Africa/Ceuta': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Africa/Conakry': 'GMT0', + 'Africa/Dakar': 'GMT0', + 'Africa/Dar es Salaam': 'EAT-3', + 'Africa/Djibouti': 'EAT-3', + 'Africa/Douala': 'WAT-1', + 'Africa/El Aaiun': '<+01>-1', + 'Africa/Freetown': 'GMT0', + 'Africa/Gaborone': 'CAT-2', + 'Africa/Harare': 'CAT-2', + 'Africa/Johannesburg': 'SAST-2', + 'Africa/Juba': 'CAT-2', + 'Africa/Kampala': 'EAT-3', + 'Africa/Khartoum': 'CAT-2', + 'Africa/Kigali': 'CAT-2', + 'Africa/Kinshasa': 'WAT-1', + 'Africa/Lagos': 'WAT-1', + 'Africa/Libreville': 'WAT-1', + 'Africa/Lome': 'GMT0', + 'Africa/Luanda': 'WAT-1', + 'Africa/Lubumbashi': 'CAT-2', + 'Africa/Lusaka': 'CAT-2', + 'Africa/Malabo': 'WAT-1', + 'Africa/Maputo': 'CAT-2', + 'Africa/Maseru': 'SAST-2', + 'Africa/Mbabane': 'SAST-2', + 'Africa/Mogadishu': 'EAT-3', + 'Africa/Monrovia': 'GMT0', + 'Africa/Nairobi': 'EAT-3', + 'Africa/Ndjamena': 'WAT-1', + 'Africa/Niamey': 'WAT-1', + 'Africa/Nouakchott': 'GMT0', + 'Africa/Ouagadougou': 'GMT0', + 'Africa/Porto-Novo': 'WAT-1', + 'Africa/Sao Tome': 'GMT0', + 'Africa/Tripoli': 'EET-2', + 'Africa/Tunis': 'CET-1', + 'Africa/Windhoek': 'CAT-2', + 'America/Adak': 'HST10HDT,M3.2.0,M11.1.0', + 'America/Anchorage': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Anguilla': 'AST4', + 'America/Antigua': 'AST4', + 'America/Araguaina': '<-03>3', + 'America/Argentina/Buenos Aires': '<-03>3', + 'America/Argentina/Catamarca': '<-03>3', + 'America/Argentina/Cordoba': '<-03>3', + 'America/Argentina/Jujuy': '<-03>3', + 'America/Argentina/La Rioja': '<-03>3', + 'America/Argentina/Mendoza': '<-03>3', + 'America/Argentina/Rio Gallegos': '<-03>3', + 'America/Argentina/Salta': '<-03>3', + 'America/Argentina/San Juan': '<-03>3', + 'America/Argentina/San Luis': '<-03>3', + 'America/Argentina/Tucuman': '<-03>3', + 'America/Argentina/Ushuaia': '<-03>3', + 'America/Aruba': 'AST4', + 'America/Asuncion': '<-04>4<-03>,M10.1.0/0,M3.4.0/0', + 'America/Atikokan': 'EST5', + 'America/Bahia': '<-03>3', + 'America/Bahia Banderas': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Barbados': 'AST4', + 'America/Belem': '<-03>3', + 'America/Belize': 'CST6', + 'America/Blanc-Sablon': 'AST4', + 'America/Boa Vista': '<-04>4', + 'America/Bogota': '<-05>5', + 'America/Boise': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Cambridge Bay': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Campo Grande': '<-04>4', + 'America/Cancun': 'EST5', + 'America/Caracas': '<-04>4', + 'America/Cayenne': '<-03>3', + 'America/Cayman': 'EST5', + 'America/Chicago': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Chihuahua': 'MST7MDT,M4.1.0,M10.5.0', + 'America/Costa Rica': 'CST6', + 'America/Creston': 'MST7', + 'America/Cuiaba': '<-04>4', + 'America/Curacao': 'AST4', + 'America/Danmarkshavn': 'GMT0', + 'America/Dawson': 'MST7', + 'America/Dawson Creek': 'MST7', + 'America/Denver': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Detroit': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Dominica': 'AST4', + 'America/Edmonton': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Eirunepe': '<-05>5', + 'America/El Salvador': 'CST6', + 'America/Fort Nelson': 'MST7', + 'America/Fortaleza': '<-03>3', + 'America/Glace Bay': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Goose Bay': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Grand Turk': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Grenada': 'AST4', + 'America/Guadeloupe': 'AST4', + 'America/Guatemala': 'CST6', + 'America/Guayaquil': '<-05>5', + 'America/Guyana': '<-04>4', + 'America/Halifax': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Havana': 'CST5CDT,M3.2.0/0,M11.1.0/1', + 'America/Hermosillo': 'MST7', + 'America/Indiana/Indianapolis': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Knox': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Indiana/Marengo': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Petersburg': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Tell City': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Indiana/Vevay': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Vincennes': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Winamac': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Inuvik': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Iqaluit': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Jamaica': 'EST5', + 'America/Juneau': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Kentucky/Louisville': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Kentucky/Monticello': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Kralendijk': 'AST4', + 'America/La Paz': '<-04>4', + 'America/Lima': '<-05>5', + 'America/Los Angeles': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Lower Princes': 'AST4', + 'America/Maceio': '<-03>3', + 'America/Managua': 'CST6', + 'America/Manaus': '<-04>4', + 'America/Marigot': 'AST4', + 'America/Martinique': 'AST4', + 'America/Matamoros': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Mazatlan': 'MST7MDT,M4.1.0,M10.5.0', + 'America/Menominee': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Merida': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Metlakatla': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Mexico City': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Miquelon': '<-03>3<-02>,M3.2.0,M11.1.0', + 'America/Moncton': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Monterrey': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Montevideo': '<-03>3', + 'America/Montserrat': 'AST4', + 'America/Nassau': 'EST5EDT,M3.2.0,M11.1.0', + 'America/New York': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Nipigon': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Nome': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Noronha': '<-02>2', + 'America/North Dakota/Beulah': 'CST6CDT,M3.2.0,M11.1.0', + 'America/North Dakota/Center': 'CST6CDT,M3.2.0,M11.1.0', + 'America/North Dakota/New Salem': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Nuuk': '<-03>3<-02>,M3.5.0/-2,M10.5.0/-1', + 'America/Ojinaga': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Panama': 'EST5', + 'America/Pangnirtung': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Paramaribo': '<-03>3', + 'America/Phoenix': 'MST7', + 'America/Port of Spain': 'AST4', + 'America/Port-au-Prince': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Porto Velho': '<-04>4', + 'America/Puerto Rico': 'AST4', + 'America/Punta Arenas': '<-03>3', + 'America/Rainy River': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Rankin Inlet': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Recife': '<-03>3', + 'America/Regina': 'CST6', + 'America/Resolute': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Rio Branco': '<-05>5', + 'America/Santarem': '<-03>3', + 'America/Santiago': '<-04>4<-03>,M9.1.6/24,M4.1.6/24', + 'America/Santo Domingo': 'AST4', + 'America/Sao Paulo': '<-03>3', + 'America/Scoresbysund': '<-01>1<+00>,M3.5.0/0,M10.5.0/1', + 'America/Sitka': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/St Barthelemy': 'AST4', + 'America/St Johns': 'NST3:30NDT,M3.2.0,M11.1.0', + 'America/St Kitts': 'AST4', + 'America/St Lucia': 'AST4', + 'America/St Thomas': 'AST4', + 'America/St Vincent': 'AST4', + 'America/Swift Current': 'CST6', + 'America/Tegucigalpa': 'CST6', + 'America/Thule': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Thunder Bay': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Tijuana': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Toronto': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Tortola': 'AST4', + 'America/Vancouver': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Whitehorse': 'MST7', + 'America/Winnipeg': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Yakutat': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Yellowknife': 'MST7MDT,M3.2.0,M11.1.0', + 'Antarctica/Casey': '<+11>-11', + 'Antarctica/Davis': '<+07>-7', + 'Antarctica/DumontDUrville': '<+10>-10', + 'Antarctica/Macquarie': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Antarctica/Mawson': '<+05>-5', + 'Antarctica/McMurdo': 'NZST-12NZDT,M9.5.0,M4.1.0/3', + 'Antarctica/Palmer': '<-03>3', + 'Antarctica/Rothera': '<-03>3', + 'Antarctica/Syowa': '<+03>-3', + 'Antarctica/Troll': '<+00>0<+02>-2,M3.5.0/1,M10.5.0/3', + 'Antarctica/Vostok': '<+06>-6', + 'Arctic/Longyearbyen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Asia/Aden': '<+03>-3', + 'Asia/Almaty': '<+06>-6', + 'Asia/Amman': '<+03>-3', + 'Asia/Anadyr': '<+12>-12', + 'Asia/Aqtau': '<+05>-5', + 'Asia/Aqtobe': '<+05>-5', + 'Asia/Ashgabat': '<+05>-5', + 'Asia/Atyrau': '<+05>-5', + 'Asia/Baghdad': '<+03>-3', + 'Asia/Bahrain': '<+03>-3', + 'Asia/Baku': '<+04>-4', + 'Asia/Bangkok': '<+07>-7', + 'Asia/Barnaul': '<+07>-7', + 'Asia/Beirut': 'EET-2EEST,M3.5.0/0,M10.5.0/0', + 'Asia/Bishkek': '<+06>-6', + 'Asia/Brunei': '<+08>-8', + 'Asia/Chita': '<+09>-9', + 'Asia/Choibalsan': '<+08>-8', + 'Asia/Colombo': '<+0530>-5:30', + 'Asia/Damascus': '<+03>-3', + 'Asia/Dhaka': '<+06>-6', + 'Asia/Dili': '<+09>-9', + 'Asia/Dubai': '<+04>-4', + 'Asia/Dushanbe': '<+05>-5', + 'Asia/Famagusta': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Asia/Gaza': 'EET-2EEST,M3.4.4/50,M10.4.4/50', + 'Asia/Hebron': 'EET-2EEST,M3.4.4/50,M10.4.4/50', + 'Asia/Ho Chi Minh': '<+07>-7', + 'Asia/Hong Kong': 'HKT-8', + 'Asia/Hovd': '<+07>-7', + 'Asia/Irkutsk': '<+08>-8', + 'Asia/Jakarta': 'WIB-7', + 'Asia/Jayapura': 'WIT-9', + 'Asia/Jerusalem': 'IST-2IDT,M3.4.4/26,M10.5.0', + 'Asia/Kabul': '<+0430>-4:30', + 'Asia/Kamchatka': '<+12>-12', + 'Asia/Karachi': 'PKT-5', + 'Asia/Kathmandu': '<+0545>-5:45', + 'Asia/Khandyga': '<+09>-9', + 'Asia/Kolkata': 'IST-5:30', + 'Asia/Krasnoyarsk': '<+07>-7', + 'Asia/Kuala Lumpur': '<+08>-8', + 'Asia/Kuching': '<+08>-8', + 'Asia/Kuwait': '<+03>-3', + 'Asia/Macau': 'CST-8', + 'Asia/Magadan': '<+11>-11', + 'Asia/Makassar': 'WITA-8', + 'Asia/Manila': 'PST-8', + 'Asia/Muscat': '<+04>-4', + 'Asia/Nicosia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Asia/Novokuznetsk': '<+07>-7', + 'Asia/Novosibirsk': '<+07>-7', + 'Asia/Omsk': '<+06>-6', + 'Asia/Oral': '<+05>-5', + 'Asia/Phnom Penh': '<+07>-7', + 'Asia/Pontianak': 'WIB-7', + 'Asia/Pyongyang': 'KST-9', + 'Asia/Qatar': '<+03>-3', + 'Asia/Qostanay': '<+06>-6', + 'Asia/Qyzylorda': '<+05>-5', + 'Asia/Riyadh': '<+03>-3', + 'Asia/Sakhalin': '<+11>-11', + 'Asia/Samarkand': '<+05>-5', + 'Asia/Seoul': 'KST-9', + 'Asia/Shanghai': 'CST-8', + 'Asia/Singapore': '<+08>-8', + 'Asia/Srednekolymsk': '<+11>-11', + 'Asia/Taipei': 'CST-8', + 'Asia/Tashkent': '<+05>-5', + 'Asia/Tbilisi': '<+04>-4', + 'Asia/Tehran': '<+0330>-3:30', + 'Asia/Thimphu': '<+06>-6', + 'Asia/Tokyo': 'JST-9', + 'Asia/Tomsk': '<+07>-7', + 'Asia/Ulaanbaatar': '<+08>-8', + 'Asia/Urumqi': '<+06>-6', + 'Asia/Ust-Nera': '<+10>-10', + 'Asia/Vientiane': '<+07>-7', + 'Asia/Vladivostok': '<+10>-10', + 'Asia/Yakutsk': '<+09>-9', + 'Asia/Yangon': '<+0630>-6:30', + 'Asia/Yekaterinburg': '<+05>-5', + 'Asia/Yerevan': '<+04>-4', + 'Atlantic/Azores': '<-01>1<+00>,M3.5.0/0,M10.5.0/1', + 'Atlantic/Bermuda': 'AST4ADT,M3.2.0,M11.1.0', + 'Atlantic/Canary': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Cape Verde': '<-01>1', + 'Atlantic/Faroe': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Madeira': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Reykjavik': 'GMT0', + 'Atlantic/South Georgia': '<-02>2', + 'Atlantic/St Helena': 'GMT0', + 'Atlantic/Stanley': '<-03>3', + 'Australia/Adelaide': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', + 'Australia/Brisbane': 'AEST-10', + 'Australia/Broken Hill': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', + 'Australia/Darwin': 'ACST-9:30', + 'Australia/Eucla': '<+0845>-8:45', + 'Australia/Hobart': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Lindeman': 'AEST-10', + 'Australia/Lord Howe': '<+1030>-10:30<+11>-11,M10.1.0,M4.1.0', + 'Australia/Melbourne': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Perth': 'AWST-8', + 'Australia/Sydney': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Etc/GMT': 'GMT0', + 'Etc/GMT+1': '<-01>1', + 'Etc/GMT+10': '<-10>10', + 'Etc/GMT+11': '<-11>11', + 'Etc/GMT+12': '<-12>12', + 'Etc/GMT+2': '<-02>2', + 'Etc/GMT+3': '<-03>3', + 'Etc/GMT+4': '<-04>4', + 'Etc/GMT+5': '<-05>5', + 'Etc/GMT+6': '<-06>6', + 'Etc/GMT+7': '<-07>7', + 'Etc/GMT+8': '<-08>8', + 'Etc/GMT+9': '<-09>9', + 'Etc/GMT-1': '<+01>-1', + 'Etc/GMT-10': '<+10>-10', + 'Etc/GMT-11': '<+11>-11', + 'Etc/GMT-12': '<+12>-12', + 'Etc/GMT-13': '<+13>-13', + 'Etc/GMT-14': '<+14>-14', + 'Etc/GMT-2': '<+02>-2', + 'Etc/GMT-3': '<+03>-3', + 'Etc/GMT-4': '<+04>-4', + 'Etc/GMT-5': '<+05>-5', + 'Etc/GMT-6': '<+06>-6', + 'Etc/GMT-7': '<+07>-7', + 'Etc/GMT-8': '<+08>-8', + 'Etc/GMT-9': '<+09>-9', + 'Europe/Amsterdam': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Andorra': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Astrakhan': '<+04>-4', + 'Europe/Athens': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Belgrade': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Berlin': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Bratislava': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Brussels': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Bucharest': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Budapest': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Busingen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Chisinau': 'EET-2EEST,M3.5.0,M10.5.0/3', + 'Europe/Copenhagen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Dublin': 'IST-1GMT0,M10.5.0,M3.5.0/1', + 'Europe/Gibraltar': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Guernsey': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Helsinki': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Isle of Man': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Istanbul': '<+03>-3', + 'Europe/Jersey': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Kaliningrad': 'EET-2', + 'Europe/Kirov': '<+03>-3', + 'Europe/Kyiv': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Lisbon': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Europe/Ljubljana': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/London': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Luxembourg': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Madrid': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Malta': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Mariehamn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Minsk': '<+03>-3', + 'Europe/Monaco': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Moscow': 'MSK-3', + 'Europe/Oslo': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Paris': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Podgorica': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Prague': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Riga': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Rome': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Samara': '<+04>-4', + 'Europe/San Marino': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Sarajevo': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Saratov': '<+04>-4', + 'Europe/Simferopol': 'MSK-3', + 'Europe/Skopje': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Sofia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Stockholm': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Tallinn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Tirane': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Ulyanovsk': '<+04>-4', + 'Europe/Vaduz': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vatican': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vienna': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vilnius': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Volgograd': '<+03>-3', + 'Europe/Warsaw': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Zagreb': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Zurich': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Indian/Antananarivo': 'EAT-3', + 'Indian/Chagos': '<+06>-6', + 'Indian/Christmas': '<+07>-7', + 'Indian/Cocos': '<+0630>-6:30', + 'Indian/Comoro': 'EAT-3', + 'Indian/Kerguelen': '<+05>-5', + 'Indian/Mahe': '<+04>-4', + 'Indian/Maldives': '<+05>-5', + 'Indian/Mauritius': '<+04>-4', + 'Indian/Mayotte': 'EAT-3', + 'Indian/Reunion': '<+04>-4', + 'Pacific/Apia': '<+13>-13', + 'Pacific/Auckland': 'NZST-12NZDT,M9.5.0,M4.1.0/3', + 'Pacific/Bougainville': '<+11>-11', + 'Pacific/Chatham': '<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45', + 'Pacific/Chuuk': '<+10>-10', + 'Pacific/Easter': '<-06>6<-05>,M9.1.6/22,M4.1.6/22', + 'Pacific/Efate': '<+11>-11', + 'Pacific/Fakaofo': '<+13>-13', + 'Pacific/Fiji': '<+12>-12<+13>,M11.2.0,M1.2.3/99', + 'Pacific/Funafuti': '<+12>-12', + 'Pacific/Galapagos': '<-06>6', + 'Pacific/Gambier': '<-09>9', + 'Pacific/Guadalcanal': '<+11>-11', + 'Pacific/Guam': 'ChST-10', + 'Pacific/Honolulu': 'HST10', + 'Pacific/Kanton': '<+13>-13', + 'Pacific/Kiritimati': '<+14>-14', + 'Pacific/Kosrae': '<+11>-11', + 'Pacific/Kwajalein': '<+12>-12', + 'Pacific/Majuro': '<+12>-12', + 'Pacific/Marquesas': '<-0930>9:30', + 'Pacific/Midway': 'SST11', + 'Pacific/Nauru': '<+12>-12', + 'Pacific/Niue': '<-11>11', + 'Pacific/Norfolk': '<+11>-11<+12>,M10.1.0,M4.1.0/3', + 'Pacific/Noumea': '<+11>-11', + 'Pacific/Pago Pago': 'SST11', + 'Pacific/Palau': '<+09>-9', + 'Pacific/Pitcairn': '<-08>8', + 'Pacific/Pohnpei': '<+11>-11', + 'Pacific/Port Moresby': '<+10>-10', + 'Pacific/Rarotonga': '<-10>10', + 'Pacific/Saipan': 'ChST-10', + 'Pacific/Tahiti': '<-10>10', + 'Pacific/Tarawa': '<+12>-12', + 'Pacific/Tongatapu': '<+13>-13', + 'Pacific/Wake': '<+12>-12', + 'Pacific/Wallis': '<+12>-12', +}; diff --git a/modules/luci-compat/Makefile b/modules/luci-compat/Makefile index d73ca070a1..4b11f16419 100644 --- a/modules/luci-compat/Makefile +++ b/modules/luci-compat/Makefile @@ -12,7 +12,7 @@ LUCI_TYPE:=mod LUCI_BASENAME:=compat LUCI_TITLE:=LuCI compatibility libraries -LUCI_DEPENDS:=+luci-base +LUCI_DEPENDS:=+luci-lua-runtime include ../../luci.mk diff --git a/modules/luci-lua-runtime/Makefile b/modules/luci-lua-runtime/Makefile new file mode 100644 index 0000000000..2c6f38f08f --- /dev/null +++ b/modules/luci-lua-runtime/Makefile @@ -0,0 +1,27 @@ +# +# Copyright (C) 2022 Jo-Philipp Wich +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-lua-runtime + +LUCI_TYPE:=mod +LUCI_BASENAME:=lua-runtime + +LUCI_TITLE:=LuCI Lua runtime libraries +LUCI_DEPENDS:= \ + +luci-base \ + +lua \ + +luci-lib-nixio \ + +luci-lib-ip \ + +luci-lib-jsonc \ + +libubus-lua \ + +liblucihttp-lua \ + +ucode-mod-lua + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/modules/luci-lua-runtime/luasrc/cacheloader.lua b/modules/luci-lua-runtime/luasrc/cacheloader.lua new file mode 100644 index 0000000000..7ef971df8d --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/cacheloader.lua @@ -0,0 +1,12 @@ +-- Copyright 2008 Steven Barth +-- Copyright 2008 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local config = require "luci.config" +local ccache = require "luci.ccache" + +module "luci.cacheloader" + +if config.ccache and config.ccache.enable == "1" then + ccache.cache_ondemand() +end diff --git a/modules/luci-lua-runtime/luasrc/ccache.lua b/modules/luci-lua-runtime/luasrc/ccache.lua new file mode 100644 index 0000000000..d3be7cba6c --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/ccache.lua @@ -0,0 +1,76 @@ +-- Copyright 2008 Steven Barth +-- Copyright 2008 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local io = require "io" +local fs = require "nixio.fs" +local util = require "luci.util" +local nixio = require "nixio" +local debug = require "debug" +local string = require "string" +local package = require "package" + +local type, loadfile = type, loadfile + + +module "luci.ccache" + +function cache_ondemand(...) + if debug.getinfo(1, 'S').source ~= "=?" then + cache_enable(...) + end +end + +function cache_enable(cachepath, mode) + cachepath = cachepath or "/tmp/luci-modulecache" + mode = mode or "r--r--r--" + + local loader = package.loaders[2] + local uid = nixio.getuid() + + if not fs.stat(cachepath) then + fs.mkdir(cachepath) + end + + local function _encode_filename(name) + local encoded = "" + for i=1, #name do + encoded = encoded .. ("%2X" % string.byte(name, i)) + end + return encoded + end + + local function _load_sane(file) + local stat = fs.stat(file) + if stat and stat.uid == uid and stat.modestr == mode then + return loadfile(file) + end + end + + local function _write_sane(file, func) + if nixio.getuid() == uid then + local fp = io.open(file, "w") + if fp then + fp:write(util.get_bytecode(func)) + fp:close() + fs.chmod(file, mode) + end + end + end + + package.loaders[2] = function(mod) + local encoded = cachepath .. "/" .. _encode_filename(mod) + local modcons = _load_sane(encoded) + + if modcons then + return modcons + end + + -- No cachefile + modcons = loader(mod) + if type(modcons) == "function" then + _write_sane(encoded, modcons) + end + return modcons + end +end diff --git a/modules/luci-lua-runtime/luasrc/config.lua b/modules/luci-lua-runtime/luasrc/config.lua new file mode 100644 index 0000000000..d01153f4f5 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/config.lua @@ -0,0 +1,18 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local util = require "luci.util" +module("luci.config", + function(m) + if pcall(require, "luci.model.uci") then + local config = util.threadlocal() + setmetatable(m, { + __index = function(tbl, key) + if not config[key] then + config[key] = luci.model.uci.cursor():get_all("luci", key) + end + return config[key] + end + }) + end + end) diff --git a/modules/luci-lua-runtime/luasrc/dispatcher.lua b/modules/luci-lua-runtime/luasrc/dispatcher.lua new file mode 100644 index 0000000000..8889853b98 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/dispatcher.lua @@ -0,0 +1,457 @@ +-- Copyright 2008 Steven Barth +-- Copyright 2008-2015 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +module("luci.dispatcher", package.seeall) + +local http = _G.L.http + +context = setmetatable({ + request = _G.L.ctx.request_path; + requested = _G.L.node; + dispatched = _G.L.node; +}, { + __index = function(t, k) + if k == "requestpath" then + return _G.L.ctx.request_path + elseif k == "requestargs" then + return _G.L.ctx.request_args + else + return _G.L.ctx[k] + end + end +}) + +uci = require "luci.model.uci" +uci:set_session_id(_G.L.ctx.authsession) + +i18n = require "luci.i18n" +i18n.setlanguage(_G.L.dispatcher.lang) + +build_url = _G.L.dispatcher.build_url +menu_json = _G.L.dispatcher.menu_json +error404 = _G.L.dispatcher.error404 +error500 = _G.L.dispatcher.error500 + +function is_authenticated(auth) + local session = _G.L.dispatcher.is_authenticated(auth) + if session then + return session.sid, session.data, session.acls + end +end + +function assign(path, clone, title, order) + local obj = node(unpack(path)) + + obj.title = title + obj.order = order + + setmetatable(obj, {__index = node(unpack(clone))}) + + return obj +end + +function entry(path, target, title, order) + local c = node(unpack(path)) + + c.title = title + c.order = order + c.action = target + + return c +end + +-- enabling the node. +function get(...) + return node(...) +end + +function node(...) + local p = table.concat({ ... }, "/") + + if not __entries[p] then + __entries[p] = {} + end + + return __entries[p] +end + +function lookup(...) + local i, path = nil, {} + for i = 1, select('#', ...) do + local name, arg = nil, tostring(select(i, ...)) + for name in arg:gmatch("[^/]+") do + path[#path+1] = name + end + end + + local node = menu_json() + for i = 1, #path do + node = node.children[path[i]] + + if not node then + return nil + elseif node.leaf then + break + end + end + + return node, build_url(unpack(path)) +end + + +function process_lua_controller(path) + local base = "/usr/lib/lua/luci/controller/" + local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".") + local mod = require(modname) + assert(mod ~= true, + "Invalid controller file found\n" .. + "The file '" .. path .. "' contains an invalid module line.\n" .. + "Please verify whether the module name is set to '" .. modname .. + "' - It must correspond to the file path!") + + local idx = mod.index + if type(idx) ~= "function" then + return nil + end + + local entries = {} + + __entries = entries + __controller = modname + + setfenv(idx, setmetatable({}, { __index = luci.dispatcher }))() + + __entries = nil + __controller = nil + + -- fixup gathered node specs + for path, entry in pairs(entries) do + if entry.leaf then + entry.wildcard = true + end + + if type(entry.file_depends) == "table" then + for _, v in ipairs(entry.file_depends) do + entry.depends = entry.depends or {} + entry.depends.fs = entry.depends.fs or {} + + local ft = fs.stat(v, "type") + if ft == "dir" then + entry.depends.fs[v] = "directory" + elseif v:match("/s?bin/") then + entry.depends.fs[v] = "executable" + else + entry.depends.fs[v] = "file" + end + end + end + + if type(entry.uci_depends) == "table" then + for k, v in pairs(entry.uci_depends) do + entry.depends = entry.depends or {} + entry.depends.uci = entry.depends.uci or {} + entry.depends.uci[k] = v + end + end + + if type(entry.acl_depends) == "table" then + for _, acl in ipairs(entry.acl_depends) do + entry.depends = entry.depends or {} + entry.depends.acl = entry.depends.acl or {} + entry.depends.acl[#entry.depends.acl + 1] = acl + end + end + + if (entry.sysauth_authenticator ~= nil) or + (entry.sysauth ~= nil and entry.sysauth ~= false) + then + if entry.sysauth_authenticator == "htmlauth" then + entry.auth = { + login = true, + methods = { "cookie:sysauth_https", "cookie:sysauth_http" } + } + elseif subname == "rpc" and entry.module == "luci.controller.rpc" then + entry.auth = { + login = false, + methods = { "query:auth", "cookie:sysauth_https", "cookie:sysauth_http" } + } + elseif entry.module == "luci.controller.admin.uci" then + entry.auth = { + login = false, + methods = { "param:sid" } + } + end + elseif entry.sysauth == false then + entry.auth = {} + end + + entry.leaf = nil + + entry.file_depends = nil + entry.uci_depends = nil + entry.acl_depends = nil + + entry.sysauth = nil + entry.sysauth_authenticator = nil + end + + return entries +end + +function invoke_cbi_action(model, config, ...) + local cbi = require "luci.cbi" + local tpl = require "luci.template" + local util = require "luci.util" + + if not config then + config = {} + end + + local maps = cbi.load(model, ...) + + local state = nil + + local function has_uci_access(config, level) + local rv = util.ubus("session", "access", { + ubus_rpc_session = context.authsession, + scope = "uci", object = config, + ["function"] = level + }) + + return (type(rv) == "table" and rv.access == true) or false + end + + local i, res + for i, res in ipairs(maps) do + if util.instanceof(res, cbi.SimpleForm) then + io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n" + % model) + + io.stderr:write("please change %s to use the form() action instead.\n" + % table.concat(context.request, "/")) + end + + res.flow = config + local cstate = res:parse() + if cstate and (not state or cstate < state) then + state = cstate + end + end + + local function _resolve_path(path) + return type(path) == "table" and build_url(unpack(path)) or path + end + + if config.on_valid_to and state and state > 0 and state < 2 then + http:redirect(_resolve_path(config.on_valid_to)) + return + end + + if config.on_changed_to and state and state > 1 then + http:redirect(_resolve_path(config.on_changed_to)) + return + end + + if config.on_success_to and state and state > 0 then + http:redirect(_resolve_path(config.on_success_to)) + return + end + + if config.state_handler then + if not config.state_handler(state, maps) then + return + end + end + + http:header("X-CBI-State", state or 0) + + if not config.noheader then + tpl.render("cbi/header", {state = state}) + end + + local redirect + local messages + local applymap = false + local pageaction = true + local parsechain = { } + local writable = false + + for i, res in ipairs(maps) do + if res.apply_needed and res.parsechain then + local c + for _, c in ipairs(res.parsechain) do + parsechain[#parsechain+1] = c + end + applymap = true + end + + if res.redirect then + redirect = redirect or res.redirect + end + + if res.pageaction == false then + pageaction = false + end + + if res.message then + messages = messages or { } + messages[#messages+1] = res.message + end + end + + for i, res in ipairs(maps) do + local is_readable_map = has_uci_access(res.config, "read") + local is_writable_map = has_uci_access(res.config, "write") + + writable = writable or is_writable_map + + res:render({ + firstmap = (i == 1), + redirect = redirect, + messages = messages, + pageaction = pageaction, + parsechain = parsechain, + readable = is_readable_map, + writable = is_writable_map + }) + end + + if not config.nofooter then + tpl.render("cbi/footer", { + flow = config, + pageaction = pageaction, + redirect = redirect, + state = state, + autoapply = config.autoapply, + trigger_apply = applymap, + writable = writable + }) + end +end + +function invoke_form_action(model, ...) + local cbi = require "luci.cbi" + local tpl = require "luci.template" + + local maps = luci.cbi.load(model, ...) + local state = nil + + local i, res + for i, res in ipairs(maps) do + local cstate = res:parse() + if cstate and (not state or cstate < state) then + state = cstate + end + end + + http:header("X-CBI-State", state or 0) + tpl.render("header") + for i, res in ipairs(maps) do + res:render() + end + tpl.render("footer") +end + + +function call(name, ...) + return { + ["type"] = "call", + ["module"] = __controller, + ["function"] = name, + ["parameters"] = select('#', ...) > 0 and {...} or nil + } +end + +function post(name, ...) + return { + ["type"] = "call", + ["module"] = __controller, + ["function"] = name, + ["parameters"] = select('#', ...) > 0 and {...} or nil, + ["post"] = true + } +end + +function view(name) + return { + ["type"] = "view", + ["path"] = name + } +end + +function template(name) + return { + ["type"] = "template", + ["path"] = name + } +end + +function cbi(model, config) + return { + ["type"] = "call", + ["module"] = "luci.dispatcher", + ["function"] = "invoke_cbi_action", + ["parameters"] = { model, config }, + ["post"] = { + ["cbi.submit"] = true + } + } +end + +function form(model) + return { + ["type"] = "call", + ["module"] = "luci.dispatcher", + ["function"] = "invoke_form_action", + ["parameters"] = { model }, + ["post"] = { + ["cbi.submit"] = true + } + } +end + +function firstchild() + return { + ["type"] = "firstchild" + } +end + +function firstnode() + return { + ["type"] = "firstchild", + ["recurse"] = true + } +end + +function arcombine(trg1, trg2) + return { + ["type"] = "arcombine", + ["targets"] = { trg1, trg2 } --, + --env = getfenv(), + } +end + +function alias(...) + return { + ["type"] = "alias", + ["path"] = table.concat({ ... }, "/") + } +end + +function rewrite(n, ...) + return { + ["type"] = "rewrite", + ["path"] = table.concat({ ... }, "/"), + ["remove"] = n + } +end + + +translate = i18n.translate + +-- This function does not actually translate the given argument but +-- is used by build/i18n-scan.pl to find translatable entries. +function _(text) + return text +end diff --git a/modules/luci-lua-runtime/luasrc/i18n.lua b/modules/luci-lua-runtime/luasrc/i18n.lua new file mode 100644 index 0000000000..323912b650 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/i18n.lua @@ -0,0 +1,55 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local tparser = require "luci.template.parser" +local util = require "luci.util" +local tostring = tostring + +module "luci.i18n" + +i18ndir = util.libpath() .. "/i18n/" +context = util.threadlocal() +default = "en" + + +function setlanguage(lang) + local code, subcode = lang:match("^([A-Za-z][A-Za-z])[%-_]([A-Za-z][A-Za-z])$") + if not (code and subcode) then + subcode = lang:match("^([A-Za-z][A-Za-z])$") + if not subcode then + return nil + end + end + + context.parent = code and code:lower() + context.lang = context.parent and context.parent.."-"..subcode:lower() or subcode:lower() + + if tparser.load_catalog(context.lang, i18ndir) and + tparser.change_catalog(context.lang) + then + return context.lang + + elseif context.parent then + if tparser.load_catalog(context.parent, i18ndir) and + tparser.change_catalog(context.parent) + then + return context.parent + end + end + + return nil +end + +function translate(key) + return tparser.translate(key) or key +end + +function translatef(key, ...) + return tostring(translate(key)):format(...) +end + +function dump() + local rv = {} + tparser.get_translations(function(k, v) rv[k] = v end) + return rv +end diff --git a/modules/luci-lua-runtime/luasrc/i18n.luadoc b/modules/luci-lua-runtime/luasrc/i18n.luadoc new file mode 100644 index 0000000000..b76c298565 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/i18n.luadoc @@ -0,0 +1,42 @@ +---[[ +LuCI translation library. +]] +module "luci.i18n" + +---[[ +Set the context default translation language. + +@class function +@name setlanguage +@param lang An IETF/BCP 47 language tag or ISO3166 country code, e.g. "en-US" or "de" +@return The effective loaded language, e.g. "en" for "en-US" - or nil on failure +]] + +---[[ +Return the translated value for a specific translation key. + +@class function +@name translate +@param key Default translation text +@return Translated string +]] + +---[[ +Return the translated value for a specific translation key and use it as sprintf pattern. + +@class function +@name translatef +@param key Default translation text +@param ... Format parameters +@return Translated and formatted string +]] + +---[[ +Return all currently loaded translation strings as a key-value table. The key is the +hexadecimal representation of the translation key while the value is the translated +text content. + +@class function +@name dump +@return Key-value translation string table. +]] diff --git a/modules/luci-lua-runtime/luasrc/model/uci.lua b/modules/luci-lua-runtime/luasrc/model/uci.lua new file mode 100644 index 0000000000..816f6f2053 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/model/uci.lua @@ -0,0 +1,508 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local os = require "os" +local util = require "luci.util" +local table = require "table" + + +local setmetatable, rawget, rawset = setmetatable, rawget, rawset +local require, getmetatable, assert = require, getmetatable, assert +local error, pairs, ipairs, select = error, pairs, ipairs, select +local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack + +-- The typical workflow for UCI is: Get a cursor instance from the +-- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), +-- save the changes to the staging area via Cursor.save and finally +-- Cursor.commit the data to the actual config files. +-- LuCI then needs to Cursor.apply the changes so daemons etc. are +-- reloaded. +module "luci.model.uci" + +local ERRSTR = { + "Invalid command", + "Invalid argument", + "Method not found", + "Entry not found", + "No data", + "Permission denied", + "Timeout", + "Not supported", + "Unknown error", + "Connection failed" +} + +local session_id = nil + +local function call(cmd, args) + if type(args) == "table" and session_id then + args.ubus_rpc_session = session_id + end + return util.ubus("uci", cmd, args) +end + + +function cursor() + return _M +end + +function cursor_state() + return _M +end + +function substate(self) + return self +end + + +function get_confdir(self) + return "/etc/config" +end + +function get_savedir(self) + return "/tmp/.uci" +end + +function get_session_id(self) + return session_id +end + +function set_confdir(self, directory) + return false +end + +function set_savedir(self, directory) + return false +end + +function set_session_id(self, id) + session_id = id + return true +end + + +function load(self, config) + return true +end + +function save(self, config) + return true +end + +function unload(self, config) + return true +end + + +function changes(self, config) + local rv, err = call("changes", { config = config }) + + if type(rv) == "table" and type(rv.changes) == "table" then + return rv.changes + elseif err then + return nil, ERRSTR[err] + else + return { } + end +end + + +function revert(self, config) + local _, err = call("revert", { config = config }) + return (err == nil), ERRSTR[err] +end + +function commit(self, config) + local _, err = call("commit", { config = config }) + return (err == nil), ERRSTR[err] +end + +function apply(self, rollback) + local _, err + + if rollback then + local sys = require "luci.sys" + local conf = require "luci.config" + local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 90) or 0 + + _, err = call("apply", { + timeout = (timeout > 90) and timeout or 90, + rollback = true + }) + + if not err then + local now = os.time() + local token = sys.uniqueid(16) + + util.ubus("session", "set", { + ubus_rpc_session = "00000000000000000000000000000000", + values = { + rollback = { + token = token, + session = session_id, + timeout = now + timeout + } + } + }) + + return token + end + else + _, err = call("changes", {}) + + if not err then + if type(_) == "table" and type(_.changes) == "table" then + local k, v + for k, v in pairs(_.changes) do + _, err = call("commit", { config = k }) + if err then + break + end + end + end + end + + if not err then + _, err = call("apply", { rollback = false }) + end + end + + return (err == nil), ERRSTR[err] +end + +function confirm(self, token) + local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending() + + if is_pending then + if token ~= rollback_token then + return false, "Permission denied" + end + + local _, err = util.ubus("uci", "confirm", { + ubus_rpc_session = rollback_sid + }) + + if not err then + util.ubus("session", "set", { + ubus_rpc_session = "00000000000000000000000000000000", + values = { rollback = {} } + }) + end + + return (err == nil), ERRSTR[err] + end + + return false, "No data" +end + +function rollback(self) + local is_pending, time_remaining, rollback_sid = self:rollback_pending() + + if is_pending then + local _, err = util.ubus("uci", "rollback", { + ubus_rpc_session = rollback_sid + }) + + if not err then + util.ubus("session", "set", { + ubus_rpc_session = "00000000000000000000000000000000", + values = { rollback = {} } + }) + end + + return (err == nil), ERRSTR[err] + end + + return false, "No data" +end + +function rollback_pending(self) + local rv, err = util.ubus("session", "get", { + ubus_rpc_session = "00000000000000000000000000000000", + keys = { "rollback" } + }) + + local now = os.time() + + if type(rv) == "table" and + type(rv.values) == "table" and + type(rv.values.rollback) == "table" and + type(rv.values.rollback.token) == "string" and + type(rv.values.rollback.session) == "string" and + type(rv.values.rollback.timeout) == "number" and + rv.values.rollback.timeout > now + then + return true, + rv.values.rollback.timeout - now, + rv.values.rollback.session, + rv.values.rollback.token + end + + return false, ERRSTR[err] +end + + +function foreach(self, config, stype, callback) + if type(callback) == "function" then + local rv, err = call("get", { + config = config, + type = stype + }) + + if type(rv) == "table" and type(rv.values) == "table" then + local sections = { } + local res = false + local index = 1 + + local _, section + for _, section in pairs(rv.values) do + section[".index"] = section[".index"] or index + sections[index] = section + index = index + 1 + end + + table.sort(sections, function(a, b) + return a[".index"] < b[".index"] + end) + + for _, section in ipairs(sections) do + local continue = callback(section) + res = true + if continue == false then + break + end + end + return res + else + return false, ERRSTR[err] or "No data" + end + else + return false, "Invalid argument" + end +end + +local function _get(self, operation, config, section, option) + if section == nil then + return nil + elseif type(option) == "string" and option:byte(1) ~= 46 then + local rv, err = call(operation, { + config = config, + section = section, + option = option + }) + + if type(rv) == "table" then + return rv.value or nil + elseif err then + return false, ERRSTR[err] + else + return nil + end + elseif option == nil then + local values = self:get_all(config, section) + if values then + return values[".type"], values[".name"] + else + return nil + end + else + return false, "Invalid argument" + end +end + +function get(self, ...) + return _get(self, "get", ...) +end + +function get_state(self, ...) + return _get(self, "state", ...) +end + +function get_all(self, config, section) + local rv, err = call("get", { + config = config, + section = section + }) + + if type(rv) == "table" and type(rv.values) == "table" then + return rv.values + elseif err then + return false, ERRSTR[err] + else + return nil + end +end + +function get_bool(self, ...) + local val = self:get(...) + return (val == "1" or val == "true" or val == "yes" or val == "on") +end + +function get_first(self, config, stype, option, default) + local rv = default + + self:foreach(config, stype, function(s) + local val = not option and s[".name"] or s[option] + + if type(default) == "number" then + val = tonumber(val) + elseif type(default) == "boolean" then + val = (val == "1" or val == "true" or + val == "yes" or val == "on") + end + + if val ~= nil then + rv = val + return false + end + end) + + return rv +end + +function get_list(self, config, section, option) + if config and section and option then + local val = self:get(config, section, option) + return (type(val) == "table" and val or { val }) + end + return { } +end + + +function section(self, config, stype, name, values) + local rv, err = call("add", { + config = config, + type = stype, + name = name, + values = values + }) + + if type(rv) == "table" then + return rv.section + elseif err then + return false, ERRSTR[err] + else + return nil + end +end + + +function add(self, config, stype) + return self:section(config, stype) +end + +function set(self, config, section, option, ...) + if select('#', ...) == 0 then + local sname, err = self:section(config, option, section) + return (not not sname), err + else + local _, err = call("set", { + config = config, + section = section, + values = { [option] = select(1, ...) } + }) + return (err == nil), ERRSTR[err] + end +end + +function set_list(self, config, section, option, value) + if section == nil or option == nil then + return false + elseif value == nil or (type(value) == "table" and #value == 0) then + return self:delete(config, section, option) + elseif type(value) == "table" then + return self:set(config, section, option, value) + else + return self:set(config, section, option, { value }) + end +end + +function tset(self, config, section, values) + local _, err = call("set", { + config = config, + section = section, + values = values + }) + return (err == nil), ERRSTR[err] +end + +function reorder(self, config, section, index) + local sections + + if type(section) == "string" and type(index) == "number" then + local pos = 0 + + sections = { } + + self:foreach(config, nil, function(s) + if pos == index then + pos = pos + 1 + end + + if s[".name"] ~= section then + pos = pos + 1 + sections[pos] = s[".name"] + else + sections[index + 1] = section + end + end) + elseif type(section) == "table" then + sections = section + else + return false, "Invalid argument" + end + + local _, err = call("order", { + config = config, + sections = sections + }) + + return (err == nil), ERRSTR[err] +end + + +function delete(self, config, section, option) + local _, err = call("delete", { + config = config, + section = section, + option = option + }) + return (err == nil), ERRSTR[err] +end + +function delete_all(self, config, stype, comparator) + local _, err + if type(comparator) == "table" then + _, err = call("delete", { + config = config, + type = stype, + match = comparator + }) + elseif type(comparator) == "function" then + local rv = call("get", { + config = config, + type = stype + }) + + if type(rv) == "table" and type(rv.values) == "table" then + local sname, section + for sname, section in pairs(rv.values) do + if comparator(section) then + _, err = call("delete", { + config = config, + section = sname + }) + end + end + end + elseif comparator == nil then + _, err = call("delete", { + config = config, + type = stype + }) + else + return false, "Invalid argument" + end + + return (err == nil), ERRSTR[err] +end diff --git a/modules/luci-lua-runtime/luasrc/model/uci.luadoc b/modules/luci-lua-runtime/luasrc/model/uci.luadoc new file mode 100644 index 0000000000..0189d49aa1 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/model/uci.luadoc @@ -0,0 +1,369 @@ +---[[ +LuCI UCI model library. + +The typical workflow for UCI is: Get a cursor instance from the +cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), +save the changes to the staging area via Cursor.save and finally +Cursor.commit the data to the actual config files. +LuCI then needs to Cursor.apply the changes so daemons etc. are +reloaded. +@cstyle instance +]] +module "luci.model.uci" + +---[[ +Create a new UCI-Cursor. + +@class function +@name cursor +@return UCI-Cursor +]] + +---[[ +Create a new Cursor initialized to the state directory. + +@class function +@name cursor_state +@return UCI cursor +]] + +---[[ +Applies UCI configuration changes. + +If the rollback parameter is set to true, the apply function will invoke the +rollback mechanism which causes the configuration to be automatically reverted +if no confirm() call occurs within a certain timeout. + +The current default timeout is 30s and can be increased using the +"luci.apply.timeout" uci configuration key. + +@class function +@name Cursor.apply +@param rollback Enable rollback mechanism +@return Boolean whether operation succeeded +]] + +---[[ +Confirms UCI apply process. + +If a previous UCI apply with rollback has been invoked using apply(true), +this function confirms the process and cancels the pending rollback timer. + +If no apply with rollback session is active, the function has no effect and +returns with a "No data" error. + +@class function +@name Cursor.confirm +@return Boolean whether operation succeeded +]] + +---[[ +Cancels UCI apply process. + +If a previous UCI apply with rollback has been invoked using apply(true), +this function cancels the process and rolls back the configuration to the +pre-apply state. + +If no apply with rollback session is active, the function has no effect and +returns with a "No data" error. + +@class function +@name Cursor.rollback +@return Boolean whether operation succeeded +]] + +---[[ +Checks whether a pending rollback is scheduled. + +If a previous UCI apply with rollback has been invoked using apply(true), +and has not been confirmed or rolled back yet, this function returns true +and the remaining time until rollback in seconds. If no rollback is pending, +the function returns false. On error, the function returns false and an +additional string describing the error. + +@class function +@name Cursor.rollback_pending +@return Boolean whether rollback is pending +@return Remaining time in seconds +]] + +---[[ +Delete all sections of a given type that match certain criteria. + +@class function +@name Cursor.delete_all +@param config UCI config +@param type UCI section type +@param comparator Function that will be called for each section and returns + a boolean whether to delete the current section (optional) +]] + +---[[ +Create a new section and initialize it with data. + +@class function +@name Cursor.section +@param config UCI config +@param type UCI section type +@param name UCI section name (optional) +@param values Table of key - value pairs to initialize the section with +@return Name of created section +]] + +---[[ +Updated the data of a section using data from a table. + +@class function +@name Cursor.tset +@param config UCI config +@param section UCI section name (optional) +@param values Table of key - value pairs to update the section with +]] + +---[[ +Get a boolean option and return it's value as true or false. + +@class function +@name Cursor.get_bool +@param config UCI config +@param section UCI section name +@param option UCI option +@return Boolean +]] + +---[[ +Get an option or list and return values as table. + +@class function +@name Cursor.get_list +@param config UCI config +@param section UCI section name +@param option UCI option +@return table. If the option was not found, you will simply get an empty + table. +]] + +---[[ +Get the given option from the first section with the given type. + +@class function +@name Cursor.get_first +@param config UCI config +@param type UCI section type +@param option UCI option (optional) +@param default Default value (optional) +@return UCI value +]] + +---[[ +Set given values as list. Setting a list option to an empty list +has the same effect as deleting the option. + +@class function +@name Cursor.set_list +@param config UCI config +@param section UCI section name +@param option UCI option +@param value Value or table. Non-table values will be set as single + item UCI list. +@return Boolean whether operation succeeded +]] + +---[[ +Create a sub-state of this cursor. + +The sub-state is tied to the parent cursor, means it the parent unloads or +loads configs, the sub state will do so as well. + +@class function +@name Cursor.substate +@return UCI state cursor tied to the parent cursor +]] + +---[[ +Add an anonymous section. + +@class function +@name Cursor.add +@param config UCI config +@param type UCI section type +@return Name of created section +]] + +---[[ +Get a table of saved but uncommitted changes. + +@class function +@name Cursor.changes +@param config UCI config +@return Table of changes +@see Cursor.save +]] + +---[[ +Commit saved changes. + +@class function +@name Cursor.commit +@param config UCI config +@return Boolean whether operation succeeded +@see Cursor.revert +@see Cursor.save +]] + +---[[ +Deletes a section or an option. + +@class function +@name Cursor.delete +@param config UCI config +@param section UCI section name +@param option UCI option (optional) +@return Boolean whether operation succeeded +]] + +---[[ +Call a function for every section of a certain type. + +@class function +@name Cursor.foreach +@param config UCI config +@param type UCI section type +@param callback Function to be called +@return Boolean whether operation succeeded +]] + +---[[ +Get a section type or an option + +@class function +@name Cursor.get +@param config UCI config +@param section UCI section name +@param option UCI option (optional) +@return UCI value +]] + +---[[ +Get all sections of a config or all values of a section. + +@class function +@name Cursor.get_all +@param config UCI config +@param section UCI section name (optional) +@return Table of UCI sections or table of UCI values +]] + +---[[ +Manually load a config. + +@class function +@name Cursor.load +@param config UCI config +@return Boolean whether operation succeeded +@see Cursor.save +@see Cursor.unload +]] + +---[[ +Revert saved but uncommitted changes. + +@class function +@name Cursor.revert +@param config UCI config +@return Boolean whether operation succeeded +@see Cursor.commit +@see Cursor.save +]] + +---[[ +Saves changes made to a config to make them committable. + +@class function +@name Cursor.save +@param config UCI config +@return Boolean whether operation succeeded +@see Cursor.load +@see Cursor.unload +]] + +---[[ +Set a value or create a named section. + +When invoked with three arguments `config`, `sectionname`, `sectiontype`, +then a named section of the given type is created. + +When invoked with four arguments `config`, `sectionname`, `optionname` and +`optionvalue` then the value of the specified option is set to the given value. + +@class function +@name Cursor.set +@param config UCI config +@param section UCI section name +@param option UCI option or UCI section type +@param value UCI value or nothing if you want to create a section +@return Boolean whether operation succeeded +]] + +---[[ +Get the configuration directory. + +@class function +@name Cursor.get_confdir +@return Configuration directory +]] + +---[[ +Get the directory for uncomitted changes. + +@class function +@name Cursor.get_savedir +@return Save directory +]] + +---[[ +Get the effective session ID. + +@class function +@name Cursor.get_session_id +@return String containing the session ID +]] + +---[[ +Set the configuration directory. + +@class function +@name Cursor.set_confdir +@param directory UCI configuration directory +@return Boolean whether operation succeeded +]] + +---[[ +Set the directory for uncommitted changes. + +@class function +@name Cursor.set_savedir +@param directory UCI changes directory +@return Boolean whether operation succeeded +]] + +---[[ +Set the effective session ID. + +@class function +@name Cursor.set_session_id +@param id String containing the session ID to set +@return Boolean whether operation succeeded +]] + +---[[ +Discard changes made to a config. + +@class function +@name Cursor.unload +@param config UCI config +@return Boolean whether operation succeeded +@see Cursor.load +@see Cursor.save +]] + diff --git a/modules/luci-lua-runtime/luasrc/sgi/cgi.lua b/modules/luci-lua-runtime/luasrc/sgi/cgi.lua new file mode 100644 index 0000000000..400db4710d --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/sgi/cgi.lua @@ -0,0 +1,73 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +exectime = os.clock() +module("luci.sgi.cgi", package.seeall) +local ltn12 = require("luci.ltn12") +require("nixio.util") +require("luci.http") +require("luci.sys") +require("luci.dispatcher") + +-- Limited source to avoid endless blocking +local function limitsource(handle, limit) + limit = limit or 0 + local BLOCKSIZE = ltn12.BLOCKSIZE + + return function() + if limit < 1 then + handle:close() + return nil + else + local read = (limit > BLOCKSIZE) and BLOCKSIZE or limit + limit = limit - read + + local chunk = handle:read(read) + if not chunk then handle:close() end + return chunk + end + end +end + +function run() + local r = luci.http.Request( + luci.sys.getenv(), + limitsource(io.stdin, tonumber(luci.sys.getenv("CONTENT_LENGTH"))), + ltn12.sink.file(io.stderr) + ) + + local x = coroutine.create(luci.dispatcher.httpdispatch) + local hcache = "" + local active = true + + while coroutine.status(x) ~= "dead" do + local res, id, data1, data2 = coroutine.resume(x, r) + + if not res then + print("Status: 500 Internal Server Error") + print("Content-Type: text/plain\n") + print(id) + break; + end + + if active then + if id == 1 then + io.write("Status: " .. tostring(data1) .. " " .. data2 .. "\r\n") + elseif id == 2 then + hcache = hcache .. data1 .. ": " .. data2 .. "\r\n" + elseif id == 3 then + io.write(hcache) + io.write("\r\n") + elseif id == 4 then + io.write(tostring(data1 or "")) + elseif id == 5 then + io.flush() + io.close() + active = false + elseif id == 6 then + data1:copyz(nixio.stdout, data2) + data1:close() + end + end + end +end diff --git a/modules/luci-lua-runtime/luasrc/sgi/uhttpd.lua b/modules/luci-lua-runtime/luasrc/sgi/uhttpd.lua new file mode 100644 index 0000000000..4cd3649c62 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/sgi/uhttpd.lua @@ -0,0 +1,99 @@ +-- Copyright 2010 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +require "nixio.util" +require "luci.http" +require "luci.sys" +require "luci.dispatcher" +require "luci.ltn12" + +function handle_request(env) + exectime = os.clock() + local renv = { + CONTENT_LENGTH = env.CONTENT_LENGTH, + CONTENT_TYPE = env.CONTENT_TYPE, + REQUEST_METHOD = env.REQUEST_METHOD, + REQUEST_URI = env.REQUEST_URI, + PATH_INFO = env.PATH_INFO, + SCRIPT_NAME = env.SCRIPT_NAME:gsub("/+$", ""), + SCRIPT_FILENAME = env.SCRIPT_NAME, + SERVER_PROTOCOL = env.SERVER_PROTOCOL, + QUERY_STRING = env.QUERY_STRING, + DOCUMENT_ROOT = env.DOCUMENT_ROOT, + HTTPS = env.HTTPS, + REDIRECT_STATUS = env.REDIRECT_STATUS, + REMOTE_ADDR = env.REMOTE_ADDR, + REMOTE_NAME = env.REMOTE_NAME, + REMOTE_PORT = env.REMOTE_PORT, + REMOTE_USER = env.REMOTE_USER, + SERVER_ADDR = env.SERVER_ADDR, + SERVER_NAME = env.SERVER_NAME, + SERVER_PORT = env.SERVER_PORT + } + + local k, v + for k, v in pairs(env.headers) do + k = k:upper():gsub("%-", "_") + renv["HTTP_" .. k] = v + end + + local len = tonumber(env.CONTENT_LENGTH) or 0 + local function recv() + if len > 0 then + local rlen, rbuf = uhttpd.recv(4096) + if rlen >= 0 then + len = len - rlen + return rbuf + end + end + return nil + end + + local send = uhttpd.send + + local req = luci.http.Request( + renv, recv, luci.ltn12.sink.file(io.stderr) + ) + + + local x = coroutine.create(luci.dispatcher.httpdispatch) + local hcache = { } + local active = true + + while coroutine.status(x) ~= "dead" do + local res, id, data1, data2 = coroutine.resume(x, req) + + if not res then + send("Status: 500 Internal Server Error\r\n") + send("Content-Type: text/plain\r\n\r\n") + send(tostring(id)) + break + end + + if active then + if id == 1 then + send("Status: ") + send(tostring(data1)) + send(" ") + send(tostring(data2)) + send("\r\n") + elseif id == 2 then + hcache[data1] = data2 + elseif id == 3 then + for k, v in pairs(hcache) do + send(tostring(k)) + send(": ") + send(tostring(v)) + send("\r\n") + end + send("\r\n") + elseif id == 4 then + send(tostring(data1 or "")) + elseif id == 5 then + active = false + elseif id == 6 then + data1:copyz(nixio.stdout, data2) + end + end + end +end diff --git a/modules/luci-lua-runtime/luasrc/store.lua b/modules/luci-lua-runtime/luasrc/store.lua new file mode 100644 index 0000000000..a735981137 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/store.lua @@ -0,0 +1,6 @@ +-- Copyright 2009 Steven Barth +-- Copyright 2009 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local util = require "luci.util" +module("luci.store", util.threadlocal) diff --git a/modules/luci-lua-runtime/luasrc/sys.lua b/modules/luci-lua-runtime/luasrc/sys.lua new file mode 100644 index 0000000000..e6eb762e48 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/sys.lua @@ -0,0 +1,615 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local io = require "io" +local os = require "os" +local table = require "table" +local nixio = require "nixio" +local fs = require "nixio.fs" +local uci = require "luci.model.uci" + +local luci = {} +luci.util = require "luci.util" +luci.ip = require "luci.ip" + +local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack = + tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack + + +module "luci.sys" + +function call(...) + return os.execute(...) / 256 +end + +exec = luci.util.exec + +-- containing the whole environment is returned otherwise this function returns +-- the corresponding string value for the given name or nil if no such variable +-- exists. +getenv = nixio.getenv + +function hostname(newname) + if type(newname) == "string" and #newname > 0 then + fs.writefile( "/proc/sys/kernel/hostname", newname ) + return newname + else + return nixio.uname().nodename + end +end + +function httpget(url, stream, target) + if not target then + local source = stream and io.popen or luci.util.exec + return source("wget -qO- %s" % luci.util.shellquote(url)) + else + return os.execute("wget -qO %s %s" % + {luci.util.shellquote(target), luci.util.shellquote(url)}) + end +end + +function reboot() + return os.execute("reboot >/dev/null 2>&1") +end + +function syslog() + return luci.util.exec("logread") +end + +function dmesg() + return luci.util.exec("dmesg") +end + +function uniqueid(bytes) + local rand = fs.readfile("/dev/urandom", bytes) + return rand and nixio.bin.hexlify(rand) +end + +function uptime() + return nixio.sysinfo().uptime +end + + +net = {} + +local function _nethints(what, callback) + local _, k, e, mac, ip, name, duid, iaid + local cur = uci.cursor() + local ifn = { } + local hosts = { } + local lookup = { } + + local function _add(i, ...) + local k = select(i, ...) + if k then + if not hosts[k] then hosts[k] = { } end + hosts[k][1] = select(1, ...) or hosts[k][1] + hosts[k][2] = select(2, ...) or hosts[k][2] + hosts[k][3] = select(3, ...) or hosts[k][3] + hosts[k][4] = select(4, ...) or hosts[k][4] + end + end + + luci.ip.neighbors(nil, function(neigh) + if neigh.mac and neigh.family == 4 then + _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil) + elseif neigh.mac and neigh.family == 6 then + _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil) + end + end) + + if fs.access("/etc/ethers") then + for e in io.lines("/etc/ethers") do + mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)") + mac = luci.ip.checkmac(mac) + if mac and name then + if luci.ip.checkip4(name) then + _add(what, mac, name, nil, nil) + else + _add(what, mac, nil, nil, name) + end + end + end + end + + cur:foreach("dhcp", "dnsmasq", + function(s) + if s.leasefile and fs.access(s.leasefile) then + for e in io.lines(s.leasefile) do + mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)") + mac = luci.ip.checkmac(mac) + if mac and ip then + _add(what, mac, ip, nil, name ~= "*" and name) + end + end + end + end + ) + + cur:foreach("dhcp", "odhcpd", + function(s) + if type(s.leasefile) == "string" and fs.access(s.leasefile) then + for e in io.lines(s.leasefile) do + duid, iaid, name, _, ip = e:match("^# %S+ (%S+) (%S+) (%S+) (-?%d+) %S+ %S+ ([0-9a-f:.]+)/[0-9]+") + mac = net.duid_to_mac(duid) + if mac then + if ip and iaid == "ipv4" then + _add(what, mac, ip, nil, name ~= "*" and name) + elseif ip then + _add(what, mac, nil, ip, name ~= "*" and name) + end + end + end + end + end + ) + + cur:foreach("dhcp", "host", + function(s) + for mac in luci.util.imatch(s.mac) do + mac = luci.ip.checkmac(mac) + if mac then + _add(what, mac, s.ip, nil, s.name) + end + end + end) + + for _, e in ipairs(nixio.getifaddrs()) do + if e.name ~= "lo" then + ifn[e.name] = ifn[e.name] or { } + if e.family == "packet" and e.addr and #e.addr == 17 then + ifn[e.name][1] = e.addr:upper() + elseif e.family == "inet" then + ifn[e.name][2] = e.addr + elseif e.family == "inet6" then + ifn[e.name][3] = e.addr + end + end + end + + for _, e in pairs(ifn) do + if e[what] and (e[2] or e[3]) then + _add(what, e[1], e[2], e[3], e[4]) + end + end + + for _, e in pairs(hosts) do + lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3]) + end + + if #lookup > 0 then + lookup = luci.util.ubus("network.rrdns", "lookup", { + addrs = lookup, + timeout = 250, + limit = 1000 + }) or { } + end + + for _, e in luci.util.kspairs(hosts) do + callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4]) + end +end + +-- Each entry contains the values in the following order: +-- [ "mac", "name" ] +function net.mac_hints(callback) + if callback then + _nethints(1, function(mac, v4, v6, name) + name = name or v4 + if name and name ~= mac then + callback(mac, name or v4) + end + end) + else + local rv = { } + _nethints(1, function(mac, v4, v6, name) + name = name or v4 + if name and name ~= mac then + rv[#rv+1] = { mac, name or v4 } + end + end) + return rv + end +end + +-- Each entry contains the values in the following order: +-- [ "ip", "name" ] +function net.ipv4_hints(callback) + if callback then + _nethints(2, function(mac, v4, v6, name) + name = name or mac + if name and name ~= v4 then + callback(v4, name) + end + end) + else + local rv = { } + _nethints(2, function(mac, v4, v6, name) + name = name or mac + if name and name ~= v4 then + rv[#rv+1] = { v4, name } + end + end) + return rv + end +end + +-- Each entry contains the values in the following order: +-- [ "ip", "name" ] +function net.ipv6_hints(callback) + if callback then + _nethints(3, function(mac, v4, v6, name) + name = name or mac + if name and name ~= v6 then + callback(v6, name) + end + end) + else + local rv = { } + _nethints(3, function(mac, v4, v6, name) + name = name or mac + if name and name ~= v6 then + rv[#rv+1] = { v6, name } + end + end) + return rv + end +end + +function net.host_hints(callback) + if callback then + _nethints(1, function(mac, v4, v6, name) + if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then + callback(mac, v4, v6, name) + end + end) + else + local rv = { } + _nethints(1, function(mac, v4, v6, name) + if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then + local e = { } + if v4 then e.ipv4 = v4 end + if v6 then e.ipv6 = v6 end + if name then e.name = name end + rv[mac] = e + end + end) + return rv + end +end + +function net.conntrack(callback) + local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack") + if not ok or not nfct then + return nil + end + + local line, connt = nil, (not callback) and { } + for line in nfct do + local fam, l3, l4, rest = + line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(.+)$") + + local timeout, tuples = rest:match("^(%d+) +(.+)$") + + if not tuples then + tuples = rest + end + + if fam and l3 and l4 and not tuples:match("^TIME_WAIT ") then + l4 = nixio.getprotobynumber(l4) + + local entry = { + bytes = 0, + packets = 0, + layer3 = fam, + layer4 = l4 and l4.name or "unknown", + timeout = tonumber(timeout, 10) + } + + local key, val + for key, val in tuples:gmatch("(%w+)=(%S+)") do + if key == "bytes" or key == "packets" then + entry[key] = entry[key] + tonumber(val, 10) + elseif key == "src" or key == "dst" then + if entry[key] == nil then + entry[key] = luci.ip.new(val):string() + end + elseif key == "sport" or key == "dport" then + if entry[key] == nil then + entry[key] = val + end + elseif val then + entry[key] = val + end + end + + if callback then + callback(entry) + else + connt[#connt+1] = entry + end + end + end + + return callback and true or connt +end + +function net.devices() + local devs = {} + local seen = {} + for k, v in ipairs(nixio.getifaddrs()) do + if v.name and not seen[v.name] then + seen[v.name] = true + devs[#devs+1] = v.name + end + end + return devs +end + +function net.duid_to_mac(duid) + local b1, b2, b3, b4, b5, b6 + + if type(duid) == "string" then + -- DUID-LLT / Ethernet + if #duid == 28 then + b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$") + + -- DUID-LL / Ethernet + elseif #duid == 20 then + b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$") + + -- DUID-LL / Ethernet (Without Header) + elseif #duid == 12 then + b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$") + end + end + + return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":")) +end + +process = {} + +function process.info(key) + local s = {uid = nixio.getuid(), gid = nixio.getgid()} + return not key and s or s[key] +end + +function process.list() + local data = {} + local k + local ps = luci.util.execi("/bin/busybox top -bn1") + + if not ps then + return + end + + for line in ps do + local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match( + "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][ 2 then + fd:close() + end +end + +function process.exec(command, stdout, stderr, nowait) + local out_r, out_w, err_r, err_w + if stdout then out_r, out_w = nixio.pipe() end + if stderr then err_r, err_w = nixio.pipe() end + + local pid = nixio.fork() + if pid == 0 then + nixio.chdir("/") + + local null = nixio.open("/dev/null", "w+") + if null then + nixio.dup(out_w or null, nixio.stdout) + nixio.dup(err_w or null, nixio.stderr) + nixio.dup(null, nixio.stdin) + xclose(out_w) + xclose(out_r) + xclose(err_w) + xclose(err_r) + xclose(null) + end + + nixio.exec(unpack(command)) + os.exit(-1) + end + + local _, pfds, rv = nil, {}, { code = -1, pid = pid } + + xclose(out_w) + xclose(err_w) + + if out_r then + pfds[#pfds+1] = { + fd = out_r, + cb = type(stdout) == "function" and stdout, + name = "stdout", + events = nixio.poll_flags("in", "err", "hup") + } + end + + if err_r then + pfds[#pfds+1] = { + fd = err_r, + cb = type(stderr) == "function" and stderr, + name = "stderr", + events = nixio.poll_flags("in", "err", "hup") + } + end + + while #pfds > 0 do + local nfds, err = nixio.poll(pfds, -1) + if not nfds and err ~= nixio.const.EINTR then + break + end + + local i + for i = #pfds, 1, -1 do + local rfd = pfds[i] + if rfd.revents > 0 then + local chunk, err = rfd.fd:read(4096) + if chunk and #chunk > 0 then + if rfd.cb then + rfd.cb(chunk) + else + rfd.buf = rfd.buf or {} + rfd.buf[#rfd.buf + 1] = chunk + end + else + table.remove(pfds, i) + if rfd.buf then + rv[rfd.name] = table.concat(rfd.buf, "") + end + rfd.fd:close() + end + end + end + end + + if not nowait then + _, _, rv.code = nixio.waitpid(pid) + end + + return rv +end + + +user = {} + +-- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } +user.getuser = nixio.getpw + +function user.getpasswd(username) + local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username) + local pwh = pwe and (pwe.pwdp or pwe.passwd) + if not pwh or #pwh < 1 then + return nil, pwe + else + return pwh, pwe + end +end + +function user.checkpasswd(username, pass) + local pwh, pwe = user.getpasswd(username) + if pwe then + return (pwh == nil or nixio.crypt(pass, pwh) == pwh) + end + return false +end + +function user.setpasswd(username, password) + return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{ + luci.util.shellquote(password), + luci.util.shellquote(password), + luci.util.shellquote(username) + }) +end + + +wifi = {} + +function wifi.getiwinfo(ifname) + local ntm = require "luci.model.network" + + ntm.init() + + local wnet = ntm:get_wifinet(ifname) + if wnet and wnet.iwinfo then + return wnet.iwinfo + end + + local wdev = ntm:get_wifidev(ifname) + if wdev and wdev.iwinfo then + return wdev.iwinfo + end + + return { ifname = ifname } +end + + +init = {} +init.dir = "/etc/init.d/" + +function init.names() + local names = { } + for name in fs.glob(init.dir.."*") do + names[#names+1] = fs.basename(name) + end + return names +end + +function init.index(name) + name = fs.basename(name) + if fs.access(init.dir..name) then + return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null" + %{ init.dir, name }) + end +end + +local function init_action(action, name) + name = fs.basename(name) + if fs.access(init.dir..name) then + return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action }) + end +end + +function init.enabled(name) + return (init_action("enabled", name) == 0) +end + +function init.enable(name) + return (init_action("enable", name) == 0) +end + +function init.disable(name) + return (init_action("disable", name) == 0) +end + +function init.start(name) + return (init_action("start", name) == 0) +end + +function init.stop(name) + return (init_action("stop", name) == 0) +end + +function init.restart(name) + return (init_action("restart", name) == 0) +end + +function init.reload(name) + return (init_action("reload", name) == 0) +end diff --git a/modules/luci-lua-runtime/luasrc/sys.luadoc b/modules/luci-lua-runtime/luasrc/sys.luadoc new file mode 100644 index 0000000000..c1e088eb23 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/sys.luadoc @@ -0,0 +1,392 @@ +---[[ +LuCI Linux and POSIX system utilities. +]] +module "luci.sys" + +---[[ +Execute a given shell command and return the error code + +@class function +@name call +@param ... Command to call +@return Error code of the command +]] + +---[[ +Execute a given shell command and capture its standard output + +@class function +@name exec +@param command Command to call +@return String containing the return the output of the command +]] + +---[[ +Retrieve information about currently mounted file systems. + +@class function +@name mounts +@return Table containing mount information +]] + +---[[ +Retrieve environment variables. If no variable is given then a table + +containing the whole environment is returned otherwise this function returns +the corresponding string value for the given name or nil if no such variable +exists. +@class function +@name getenv +@param var Name of the environment variable to retrieve (optional) +@return String containing the value of the specified variable +@return Table containing all variables if no variable name is given +]] + +---[[ +Get or set the current hostname. + +@class function +@name hostname +@param String containing a new hostname to set (optional) +@return String containing the system hostname +]] + +---[[ +Returns the contents of a documented referred by an URL. + +@class function +@name httpget +@param url The URL to retrieve +@param stream Return a stream instead of a buffer +@param target Directly write to target file name +@return String containing the contents of given the URL +]] + +---[[ +Initiate a system reboot. + +@class function +@name reboot +@return Return value of os.execute() +]] + +---[[ +Retrieves the output of the "logread" command. + +@class function +@name syslog +@return String containing the current log buffer +]] + +---[[ +Retrieves the output of the "dmesg" command. + +@class function +@name dmesg +@return String containing the current log buffer +]] + +---[[ +Generates a random id with specified length. + +@class function +@name uniqueid +@param bytes Number of bytes for the unique id +@return String containing hex encoded id +]] + +---[[ +Returns the current system uptime stats. + +@class function +@name uptime +@return String containing total uptime in seconds +]] + +---[[ +LuCI system utilities / network related functions. + +@class module +@name luci.sys.net +]] + +---[[ +Returns a two-dimensional table of mac address hints. + +@class function +@name net.mac_hints +@return Table of table containing known hosts from various sources. + Each entry contains the values in the following order: + [ "mac", "name" ] +]] + +---[[ +Returns a two-dimensional table of IPv4 address hints. + +@class function +@name net.ipv4_hints +@return Table of table containing known hosts from various sources. + Each entry contains the values in the following order: + [ "ip", "name" ] +]] + +---[[ +Returns a two-dimensional table of IPv6 address hints. + +@class function +@name net.ipv6_hints +@return Table of table containing known hosts from various sources. + Each entry contains the values in the following order: + [ "ip", "name" ] +]] + +---[[ +Returns a two-dimensional table of host hints. + +@class function +@name net.host_hints +@return Table of table containing known hosts from various sources, + indexed by mac address. Each subtable contains at least one + of the fields "name", "ipv4" or "ipv6". +]] + +---[[ +Returns conntrack information + +@class function +@name net.conntrack +@return Table with the currently tracked IP connections +]] + +---[[ +Determine the names of available network interfaces. + +@class function +@name net.devices +@return Table containing all current interface names +]] + +---[[ +LuCI system utilities / process related functions. + +@class module +@name luci.sys.process +]] + +---[[ +Get the current process id. + +@class function +@name process.info +@return Number containing the current pid +]] + +---[[ +Retrieve information about currently running processes. + +@class function +@name process.list +@return Table containing process information +]] + +---[[ +Set the gid of a process identified by given pid. + +@class function +@name process.setgroup +@param gid Number containing the Unix group id +@return Boolean indicating successful operation +@return String containing the error message if failed +@return Number containing the error code if failed +]] + +---[[ +Set the uid of a process identified by given pid. + +@class function +@name process.setuser +@param uid Number containing the Unix user id +@return Boolean indicating successful operation +@return String containing the error message if failed +@return Number containing the error code if failed +]] + +---[[ +Send a signal to a process identified by given pid. + +@class function +@name process.signal +@param pid Number containing the process id +@param sig Signal to send (default: 15 [SIGTERM]) +@return Boolean indicating successful operation +@return Number containing the error code if failed +]] + +---[[ +Execute a process, optionally capturing stdio. + +Executes the process specified by the given argv vector, e.g. +`{ "/bin/sh", "-c", "echo 1" }` and waits for it to terminate unless a true +value has been passed for the "nowait" parameter. + +When a function value is passed for the stdout or stderr arguments, the passed +function is repeatedly called for each chunk read from the corresponding stdio +stream. The read data is passed as string containing at most 4096 bytes at a +time. + +When a true, non-function value is passed for the stdout or stderr arguments, +the data of the corresponding stdio stream is read into an internal string +buffer and returned as "stdout" or "stderr" field respectively in the result +table. + +When a true value is passed to the nowait parameter, the function does not +await process termination but returns as soon as all captured stdio streams +have been closed or - if no streams are captured - immediately after launching +the process. + +@class function +@name process.exec +@param commend Table containing the argv vector to execute +@param stdout Callback function or boolean to indicate capturing (optional) +@param stderr Callback function or boolean to indicate capturing (optional) +@param nowait Don't wait for process termination when true (optional) +@return Table containing at least the fields "code" which holds the exit + status of the invoked process or "-1" on error and "pid", which + contains the process id assigned to the spawned process. When + stdout and/or stderr capturing has been requested, it additionally + contains "stdout" and "stderr" fields respectively, holding the + captured stdio data as string. +]] + +---[[ +LuCI system utilities / user related functions. + +@class module +@name luci.sys.user +]] + +---[[ +Retrieve user information for given uid. + +@class function +@name getuser +@param uid Number containing the Unix user id +@return Table containing the following fields: +-- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } +]] + +---[[ +Retrieve the current user password hash. + +@class function +@name user.getpasswd +@param username String containing the username to retrieve the password for +@return String containing the hash or nil if no password is set. +@return Password database entry +]] + +---[[ +Test whether given string matches the password of a given system user. + +@class function +@name user.checkpasswd +@param username String containing the Unix user name +@param pass String containing the password to compare +@return Boolean indicating whether the passwords are equal +]] + +---[[ +Change the password of given user. + +@class function +@name user.setpasswd +@param username String containing the Unix user name +@param password String containing the password to compare +@return Number containing 0 on success and >= 1 on error +]] + +---[[ +LuCI system utilities / wifi related functions. + +@class module +@name luci.sys.wifi +]] + +---[[ +Get wireless information for given interface. + +@class function +@name wifi.getiwinfo +@param ifname String containing the interface name +@return A wrapped iwinfo object instance +]] + +---[[ +LuCI system utilities / init related functions. + +@class module +@name luci.sys.init +]] + +---[[ +Get the names of all installed init scripts + +@class function +@name init.names +@return Table containing the names of all inistalled init scripts +]] + +---[[ +Get the index of he given init script + +@class function +@name init.index +@param name Name of the init script +@return Numeric index value +]] + +---[[ +Test whether the given init script is enabled + +@class function +@name init.enabled +@param name Name of the init script +@return Boolean indicating whether init is enabled +]] + +---[[ +Enable the given init script + +@class function +@name init.enable +@param name Name of the init script +@return Boolean indicating success +]] + +---[[ +Disable the given init script + +@class function +@name init.disable +@param name Name of the init script +@return Boolean indicating success +]] + +---[[ +Start the given init script + +@class function +@name init.start +@param name Name of the init script +@return Boolean indicating success +]] + +---[[ +Stop the given init script + +@class function +@name init.stop +@param name Name of the init script +@return Boolean indicating success +]] + diff --git a/modules/luci-lua-runtime/luasrc/sys/zoneinfo.lua b/modules/luci-lua-runtime/luasrc/sys/zoneinfo.lua new file mode 100644 index 0000000000..aa054a246f --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/sys/zoneinfo.lua @@ -0,0 +1,19 @@ +-- Licensed to the public under the Apache License 2.0. + +local setmetatable, require, rawget, rawset = setmetatable, require, rawget, rawset + +module "luci.sys.zoneinfo" + +setmetatable(_M, { + __index = function(t, k) + if k == "TZ" and not rawget(t, k) then + local m = require "luci.sys.zoneinfo.tzdata" + rawset(t, k, rawget(m, k)) + elseif k == "OFFSET" and not rawget(t, k) then + local m = require "luci.sys.zoneinfo.tzoffset" + rawset(t, k, rawget(m, k)) + end + + return rawget(t, k) + end +}) diff --git a/modules/luci-lua-runtime/luasrc/sys/zoneinfo/tzdata.lua b/modules/luci-lua-runtime/luasrc/sys/zoneinfo/tzdata.lua new file mode 100644 index 0000000000..3ef2f4caf4 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/sys/zoneinfo/tzdata.lua @@ -0,0 +1,455 @@ +-- Licensed to the public under the Apache License 2.0. + +module "luci.sys.zoneinfo.tzdata" + +TZ = { + { 'Africa/Abidjan', 'GMT0' }, + { 'Africa/Accra', 'GMT0' }, + { 'Africa/Addis Ababa', 'EAT-3' }, + { 'Africa/Algiers', 'CET-1' }, + { 'Africa/Asmara', 'EAT-3' }, + { 'Africa/Bamako', 'GMT0' }, + { 'Africa/Bangui', 'WAT-1' }, + { 'Africa/Banjul', 'GMT0' }, + { 'Africa/Bissau', 'GMT0' }, + { 'Africa/Blantyre', 'CAT-2' }, + { 'Africa/Brazzaville', 'WAT-1' }, + { 'Africa/Bujumbura', 'CAT-2' }, + { 'Africa/Cairo', 'EET-2' }, + { 'Africa/Casablanca', '<+01>-1' }, + { 'Africa/Ceuta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Africa/Conakry', 'GMT0' }, + { 'Africa/Dakar', 'GMT0' }, + { 'Africa/Dar es Salaam', 'EAT-3' }, + { 'Africa/Djibouti', 'EAT-3' }, + { 'Africa/Douala', 'WAT-1' }, + { 'Africa/El Aaiun', '<+01>-1' }, + { 'Africa/Freetown', 'GMT0' }, + { 'Africa/Gaborone', 'CAT-2' }, + { 'Africa/Harare', 'CAT-2' }, + { 'Africa/Johannesburg', 'SAST-2' }, + { 'Africa/Juba', 'CAT-2' }, + { 'Africa/Kampala', 'EAT-3' }, + { 'Africa/Khartoum', 'CAT-2' }, + { 'Africa/Kigali', 'CAT-2' }, + { 'Africa/Kinshasa', 'WAT-1' }, + { 'Africa/Lagos', 'WAT-1' }, + { 'Africa/Libreville', 'WAT-1' }, + { 'Africa/Lome', 'GMT0' }, + { 'Africa/Luanda', 'WAT-1' }, + { 'Africa/Lubumbashi', 'CAT-2' }, + { 'Africa/Lusaka', 'CAT-2' }, + { 'Africa/Malabo', 'WAT-1' }, + { 'Africa/Maputo', 'CAT-2' }, + { 'Africa/Maseru', 'SAST-2' }, + { 'Africa/Mbabane', 'SAST-2' }, + { 'Africa/Mogadishu', 'EAT-3' }, + { 'Africa/Monrovia', 'GMT0' }, + { 'Africa/Nairobi', 'EAT-3' }, + { 'Africa/Ndjamena', 'WAT-1' }, + { 'Africa/Niamey', 'WAT-1' }, + { 'Africa/Nouakchott', 'GMT0' }, + { 'Africa/Ouagadougou', 'GMT0' }, + { 'Africa/Porto-Novo', 'WAT-1' }, + { 'Africa/Sao Tome', 'GMT0' }, + { 'Africa/Tripoli', 'EET-2' }, + { 'Africa/Tunis', 'CET-1' }, + { 'Africa/Windhoek', 'CAT-2' }, + { 'America/Adak', 'HST10HDT,M3.2.0,M11.1.0' }, + { 'America/Anchorage', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Anguilla', 'AST4' }, + { 'America/Antigua', 'AST4' }, + { 'America/Araguaina', '<-03>3' }, + { 'America/Argentina/Buenos Aires', '<-03>3' }, + { 'America/Argentina/Catamarca', '<-03>3' }, + { 'America/Argentina/Cordoba', '<-03>3' }, + { 'America/Argentina/Jujuy', '<-03>3' }, + { 'America/Argentina/La Rioja', '<-03>3' }, + { 'America/Argentina/Mendoza', '<-03>3' }, + { 'America/Argentina/Rio Gallegos', '<-03>3' }, + { 'America/Argentina/Salta', '<-03>3' }, + { 'America/Argentina/San Juan', '<-03>3' }, + { 'America/Argentina/San Luis', '<-03>3' }, + { 'America/Argentina/Tucuman', '<-03>3' }, + { 'America/Argentina/Ushuaia', '<-03>3' }, + { 'America/Aruba', 'AST4' }, + { 'America/Asuncion', '<-04>4<-03>,M10.1.0/0,M3.4.0/0' }, + { 'America/Atikokan', 'EST5' }, + { 'America/Bahia', '<-03>3' }, + { 'America/Bahia Banderas', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Barbados', 'AST4' }, + { 'America/Belem', '<-03>3' }, + { 'America/Belize', 'CST6' }, + { 'America/Blanc-Sablon', 'AST4' }, + { 'America/Boa Vista', '<-04>4' }, + { 'America/Bogota', '<-05>5' }, + { 'America/Boise', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Cambridge Bay', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Campo Grande', '<-04>4' }, + { 'America/Cancun', 'EST5' }, + { 'America/Caracas', '<-04>4' }, + { 'America/Cayenne', '<-03>3' }, + { 'America/Cayman', 'EST5' }, + { 'America/Chicago', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Chihuahua', 'MST7MDT,M4.1.0,M10.5.0' }, + { 'America/Costa Rica', 'CST6' }, + { 'America/Creston', 'MST7' }, + { 'America/Cuiaba', '<-04>4' }, + { 'America/Curacao', 'AST4' }, + { 'America/Danmarkshavn', 'GMT0' }, + { 'America/Dawson', 'MST7' }, + { 'America/Dawson Creek', 'MST7' }, + { 'America/Denver', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Detroit', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Dominica', 'AST4' }, + { 'America/Edmonton', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Eirunepe', '<-05>5' }, + { 'America/El Salvador', 'CST6' }, + { 'America/Fort Nelson', 'MST7' }, + { 'America/Fortaleza', '<-03>3' }, + { 'America/Glace Bay', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Goose Bay', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Grand Turk', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Grenada', 'AST4' }, + { 'America/Guadeloupe', 'AST4' }, + { 'America/Guatemala', 'CST6' }, + { 'America/Guayaquil', '<-05>5' }, + { 'America/Guyana', '<-04>4' }, + { 'America/Halifax', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Havana', 'CST5CDT,M3.2.0/0,M11.1.0/1' }, + { 'America/Hermosillo', 'MST7' }, + { 'America/Indiana/Indianapolis', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Knox', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Marengo', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Petersburg', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Tell City', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Vevay', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Vincennes', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Winamac', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Inuvik', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Iqaluit', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Jamaica', 'EST5' }, + { 'America/Juneau', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Kentucky/Louisville', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Kentucky/Monticello', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Kralendijk', 'AST4' }, + { 'America/La Paz', '<-04>4' }, + { 'America/Lima', '<-05>5' }, + { 'America/Los Angeles', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Lower Princes', 'AST4' }, + { 'America/Maceio', '<-03>3' }, + { 'America/Managua', 'CST6' }, + { 'America/Manaus', '<-04>4' }, + { 'America/Marigot', 'AST4' }, + { 'America/Martinique', 'AST4' }, + { 'America/Matamoros', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Mazatlan', 'MST7MDT,M4.1.0,M10.5.0' }, + { 'America/Menominee', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Merida', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Metlakatla', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Mexico City', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Miquelon', '<-03>3<-02>,M3.2.0,M11.1.0' }, + { 'America/Moncton', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Monterrey', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Montevideo', '<-03>3' }, + { 'America/Montserrat', 'AST4' }, + { 'America/Nassau', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/New York', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Nipigon', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Nome', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Noronha', '<-02>2' }, + { 'America/North Dakota/Beulah', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/North Dakota/Center', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/North Dakota/New Salem', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Nuuk', '<-03>3<-02>,M3.5.0/-2,M10.5.0/-1' }, + { 'America/Ojinaga', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Panama', 'EST5' }, + { 'America/Pangnirtung', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Paramaribo', '<-03>3' }, + { 'America/Phoenix', 'MST7' }, + { 'America/Port of Spain', 'AST4' }, + { 'America/Port-au-Prince', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Porto Velho', '<-04>4' }, + { 'America/Puerto Rico', 'AST4' }, + { 'America/Punta Arenas', '<-03>3' }, + { 'America/Rainy River', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Rankin Inlet', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Recife', '<-03>3' }, + { 'America/Regina', 'CST6' }, + { 'America/Resolute', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Rio Branco', '<-05>5' }, + { 'America/Santarem', '<-03>3' }, + { 'America/Santiago', '<-04>4<-03>,M9.1.6/24,M4.1.6/24' }, + { 'America/Santo Domingo', 'AST4' }, + { 'America/Sao Paulo', '<-03>3' }, + { 'America/Scoresbysund', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' }, + { 'America/Sitka', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/St Barthelemy', 'AST4' }, + { 'America/St Johns', 'NST3:30NDT,M3.2.0,M11.1.0' }, + { 'America/St Kitts', 'AST4' }, + { 'America/St Lucia', 'AST4' }, + { 'America/St Thomas', 'AST4' }, + { 'America/St Vincent', 'AST4' }, + { 'America/Swift Current', 'CST6' }, + { 'America/Tegucigalpa', 'CST6' }, + { 'America/Thule', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Thunder Bay', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Tijuana', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Toronto', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Tortola', 'AST4' }, + { 'America/Vancouver', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Whitehorse', 'MST7' }, + { 'America/Winnipeg', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Yakutat', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Yellowknife', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'Antarctica/Casey', '<+11>-11' }, + { 'Antarctica/Davis', '<+07>-7' }, + { 'Antarctica/DumontDUrville', '<+10>-10' }, + { 'Antarctica/Macquarie', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, + { 'Antarctica/Mawson', '<+05>-5' }, + { 'Antarctica/McMurdo', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, + { 'Antarctica/Palmer', '<-03>3' }, + { 'Antarctica/Rothera', '<-03>3' }, + { 'Antarctica/Syowa', '<+03>-3' }, + { 'Antarctica/Troll', '<+00>0<+02>-2,M3.5.0/1,M10.5.0/3' }, + { 'Antarctica/Vostok', '<+06>-6' }, + { 'Arctic/Longyearbyen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Asia/Aden', '<+03>-3' }, + { 'Asia/Almaty', '<+06>-6' }, + { 'Asia/Amman', '<+03>-3' }, + { 'Asia/Anadyr', '<+12>-12' }, + { 'Asia/Aqtau', '<+05>-5' }, + { 'Asia/Aqtobe', '<+05>-5' }, + { 'Asia/Ashgabat', '<+05>-5' }, + { 'Asia/Atyrau', '<+05>-5' }, + { 'Asia/Baghdad', '<+03>-3' }, + { 'Asia/Bahrain', '<+03>-3' }, + { 'Asia/Baku', '<+04>-4' }, + { 'Asia/Bangkok', '<+07>-7' }, + { 'Asia/Barnaul', '<+07>-7' }, + { 'Asia/Beirut', 'EET-2EEST,M3.5.0/0,M10.5.0/0' }, + { 'Asia/Bishkek', '<+06>-6' }, + { 'Asia/Brunei', '<+08>-8' }, + { 'Asia/Chita', '<+09>-9' }, + { 'Asia/Choibalsan', '<+08>-8' }, + { 'Asia/Colombo', '<+0530>-5:30' }, + { 'Asia/Damascus', '<+03>-3' }, + { 'Asia/Dhaka', '<+06>-6' }, + { 'Asia/Dili', '<+09>-9' }, + { 'Asia/Dubai', '<+04>-4' }, + { 'Asia/Dushanbe', '<+05>-5' }, + { 'Asia/Famagusta', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Asia/Gaza', 'EET-2EEST,M3.4.4/50,M10.4.4/50' }, + { 'Asia/Hebron', 'EET-2EEST,M3.4.4/50,M10.4.4/50' }, + { 'Asia/Ho Chi Minh', '<+07>-7' }, + { 'Asia/Hong Kong', 'HKT-8' }, + { 'Asia/Hovd', '<+07>-7' }, + { 'Asia/Irkutsk', '<+08>-8' }, + { 'Asia/Jakarta', 'WIB-7' }, + { 'Asia/Jayapura', 'WIT-9' }, + { 'Asia/Jerusalem', 'IST-2IDT,M3.4.4/26,M10.5.0' }, + { 'Asia/Kabul', '<+0430>-4:30' }, + { 'Asia/Kamchatka', '<+12>-12' }, + { 'Asia/Karachi', 'PKT-5' }, + { 'Asia/Kathmandu', '<+0545>-5:45' }, + { 'Asia/Khandyga', '<+09>-9' }, + { 'Asia/Kolkata', 'IST-5:30' }, + { 'Asia/Krasnoyarsk', '<+07>-7' }, + { 'Asia/Kuala Lumpur', '<+08>-8' }, + { 'Asia/Kuching', '<+08>-8' }, + { 'Asia/Kuwait', '<+03>-3' }, + { 'Asia/Macau', 'CST-8' }, + { 'Asia/Magadan', '<+11>-11' }, + { 'Asia/Makassar', 'WITA-8' }, + { 'Asia/Manila', 'PST-8' }, + { 'Asia/Muscat', '<+04>-4' }, + { 'Asia/Nicosia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Asia/Novokuznetsk', '<+07>-7' }, + { 'Asia/Novosibirsk', '<+07>-7' }, + { 'Asia/Omsk', '<+06>-6' }, + { 'Asia/Oral', '<+05>-5' }, + { 'Asia/Phnom Penh', '<+07>-7' }, + { 'Asia/Pontianak', 'WIB-7' }, + { 'Asia/Pyongyang', 'KST-9' }, + { 'Asia/Qatar', '<+03>-3' }, + { 'Asia/Qostanay', '<+06>-6' }, + { 'Asia/Qyzylorda', '<+05>-5' }, + { 'Asia/Riyadh', '<+03>-3' }, + { 'Asia/Sakhalin', '<+11>-11' }, + { 'Asia/Samarkand', '<+05>-5' }, + { 'Asia/Seoul', 'KST-9' }, + { 'Asia/Shanghai', 'CST-8' }, + { 'Asia/Singapore', '<+08>-8' }, + { 'Asia/Srednekolymsk', '<+11>-11' }, + { 'Asia/Taipei', 'CST-8' }, + { 'Asia/Tashkent', '<+05>-5' }, + { 'Asia/Tbilisi', '<+04>-4' }, + { 'Asia/Tehran', '<+0330>-3:30' }, + { 'Asia/Thimphu', '<+06>-6' }, + { 'Asia/Tokyo', 'JST-9' }, + { 'Asia/Tomsk', '<+07>-7' }, + { 'Asia/Ulaanbaatar', '<+08>-8' }, + { 'Asia/Urumqi', '<+06>-6' }, + { 'Asia/Ust-Nera', '<+10>-10' }, + { 'Asia/Vientiane', '<+07>-7' }, + { 'Asia/Vladivostok', '<+10>-10' }, + { 'Asia/Yakutsk', '<+09>-9' }, + { 'Asia/Yangon', '<+0630>-6:30' }, + { 'Asia/Yekaterinburg', '<+05>-5' }, + { 'Asia/Yerevan', '<+04>-4' }, + { 'Atlantic/Azores', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' }, + { 'Atlantic/Bermuda', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'Atlantic/Canary', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Cape Verde', '<-01>1' }, + { 'Atlantic/Faroe', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Madeira', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Reykjavik', 'GMT0' }, + { 'Atlantic/South Georgia', '<-02>2' }, + { 'Atlantic/St Helena', 'GMT0' }, + { 'Atlantic/Stanley', '<-03>3' }, + { 'Australia/Adelaide', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' }, + { 'Australia/Brisbane', 'AEST-10' }, + { 'Australia/Broken Hill', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' }, + { 'Australia/Darwin', 'ACST-9:30' }, + { 'Australia/Eucla', '<+0845>-8:45' }, + { 'Australia/Hobart', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, + { 'Australia/Lindeman', 'AEST-10' }, + { 'Australia/Lord Howe', '<+1030>-10:30<+11>-11,M10.1.0,M4.1.0' }, + { 'Australia/Melbourne', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, + { 'Australia/Perth', 'AWST-8' }, + { 'Australia/Sydney', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, + { 'Etc/GMT', 'GMT0' }, + { 'Etc/GMT+1', '<-01>1' }, + { 'Etc/GMT+10', '<-10>10' }, + { 'Etc/GMT+11', '<-11>11' }, + { 'Etc/GMT+12', '<-12>12' }, + { 'Etc/GMT+2', '<-02>2' }, + { 'Etc/GMT+3', '<-03>3' }, + { 'Etc/GMT+4', '<-04>4' }, + { 'Etc/GMT+5', '<-05>5' }, + { 'Etc/GMT+6', '<-06>6' }, + { 'Etc/GMT+7', '<-07>7' }, + { 'Etc/GMT+8', '<-08>8' }, + { 'Etc/GMT+9', '<-09>9' }, + { 'Etc/GMT-1', '<+01>-1' }, + { 'Etc/GMT-10', '<+10>-10' }, + { 'Etc/GMT-11', '<+11>-11' }, + { 'Etc/GMT-12', '<+12>-12' }, + { 'Etc/GMT-13', '<+13>-13' }, + { 'Etc/GMT-14', '<+14>-14' }, + { 'Etc/GMT-2', '<+02>-2' }, + { 'Etc/GMT-3', '<+03>-3' }, + { 'Etc/GMT-4', '<+04>-4' }, + { 'Etc/GMT-5', '<+05>-5' }, + { 'Etc/GMT-6', '<+06>-6' }, + { 'Etc/GMT-7', '<+07>-7' }, + { 'Etc/GMT-8', '<+08>-8' }, + { 'Etc/GMT-9', '<+09>-9' }, + { 'Europe/Amsterdam', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Andorra', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Astrakhan', '<+04>-4' }, + { 'Europe/Athens', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Belgrade', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Berlin', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Bratislava', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Brussels', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Bucharest', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Budapest', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Busingen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Chisinau', 'EET-2EEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Copenhagen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Dublin', 'IST-1GMT0,M10.5.0,M3.5.0/1' }, + { 'Europe/Gibraltar', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Guernsey', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Helsinki', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Isle of Man', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Istanbul', '<+03>-3' }, + { 'Europe/Jersey', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Kaliningrad', 'EET-2' }, + { 'Europe/Kirov', '<+03>-3' }, + { 'Europe/Kyiv', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Lisbon', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Europe/Ljubljana', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/London', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Luxembourg', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Madrid', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Malta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Mariehamn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Minsk', '<+03>-3' }, + { 'Europe/Monaco', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Moscow', 'MSK-3' }, + { 'Europe/Oslo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Paris', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Podgorica', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Prague', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Riga', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Rome', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Samara', '<+04>-4' }, + { 'Europe/San Marino', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Sarajevo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Saratov', '<+04>-4' }, + { 'Europe/Simferopol', 'MSK-3' }, + { 'Europe/Skopje', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Sofia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Stockholm', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Tallinn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Tirane', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Ulyanovsk', '<+04>-4' }, + { 'Europe/Vaduz', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vatican', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vienna', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vilnius', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Volgograd', '<+03>-3' }, + { 'Europe/Warsaw', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Zagreb', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Zurich', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Indian/Antananarivo', 'EAT-3' }, + { 'Indian/Chagos', '<+06>-6' }, + { 'Indian/Christmas', '<+07>-7' }, + { 'Indian/Cocos', '<+0630>-6:30' }, + { 'Indian/Comoro', 'EAT-3' }, + { 'Indian/Kerguelen', '<+05>-5' }, + { 'Indian/Mahe', '<+04>-4' }, + { 'Indian/Maldives', '<+05>-5' }, + { 'Indian/Mauritius', '<+04>-4' }, + { 'Indian/Mayotte', 'EAT-3' }, + { 'Indian/Reunion', '<+04>-4' }, + { 'Pacific/Apia', '<+13>-13' }, + { 'Pacific/Auckland', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, + { 'Pacific/Bougainville', '<+11>-11' }, + { 'Pacific/Chatham', '<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45' }, + { 'Pacific/Chuuk', '<+10>-10' }, + { 'Pacific/Easter', '<-06>6<-05>,M9.1.6/22,M4.1.6/22' }, + { 'Pacific/Efate', '<+11>-11' }, + { 'Pacific/Fakaofo', '<+13>-13' }, + { 'Pacific/Fiji', '<+12>-12<+13>,M11.2.0,M1.2.3/99' }, + { 'Pacific/Funafuti', '<+12>-12' }, + { 'Pacific/Galapagos', '<-06>6' }, + { 'Pacific/Gambier', '<-09>9' }, + { 'Pacific/Guadalcanal', '<+11>-11' }, + { 'Pacific/Guam', 'ChST-10' }, + { 'Pacific/Honolulu', 'HST10' }, + { 'Pacific/Kanton', '<+13>-13' }, + { 'Pacific/Kiritimati', '<+14>-14' }, + { 'Pacific/Kosrae', '<+11>-11' }, + { 'Pacific/Kwajalein', '<+12>-12' }, + { 'Pacific/Majuro', '<+12>-12' }, + { 'Pacific/Marquesas', '<-0930>9:30' }, + { 'Pacific/Midway', 'SST11' }, + { 'Pacific/Nauru', '<+12>-12' }, + { 'Pacific/Niue', '<-11>11' }, + { 'Pacific/Norfolk', '<+11>-11<+12>,M10.1.0,M4.1.0/3' }, + { 'Pacific/Noumea', '<+11>-11' }, + { 'Pacific/Pago Pago', 'SST11' }, + { 'Pacific/Palau', '<+09>-9' }, + { 'Pacific/Pitcairn', '<-08>8' }, + { 'Pacific/Pohnpei', '<+11>-11' }, + { 'Pacific/Port Moresby', '<+10>-10' }, + { 'Pacific/Rarotonga', '<-10>10' }, + { 'Pacific/Saipan', 'ChST-10' }, + { 'Pacific/Tahiti', '<-10>10' }, + { 'Pacific/Tarawa', '<+12>-12' }, + { 'Pacific/Tongatapu', '<+13>-13' }, + { 'Pacific/Wake', '<+12>-12' }, + { 'Pacific/Wallis', '<+12>-12' }, +} diff --git a/modules/luci-lua-runtime/luasrc/sys/zoneinfo/tzoffset.lua b/modules/luci-lua-runtime/luasrc/sys/zoneinfo/tzoffset.lua new file mode 100644 index 0000000000..caee1d2c1c --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/sys/zoneinfo/tzoffset.lua @@ -0,0 +1,46 @@ +-- Licensed to the public under the Apache License 2.0. + +module "luci.sys.zoneinfo.tzoffset" + +OFFSET = { + gmt = 0, -- GMT + eat = 10800, -- EAT + cet = 3600, -- CET + wat = 3600, -- WAT + cat = 7200, -- CAT + eet = 7200, -- EET + sast = 7200, -- SAST + hst = -36000, -- HST + hdt = -32400, -- HDT + akst = -32400, -- AKST + akdt = -28800, -- AKDT + ast = -14400, -- AST + est = -18000, -- EST + cst = -21600, -- CST + cdt = -18000, -- CDT + mst = -25200, -- MST + mdt = -21600, -- MDT + pst = -28800, -- PST + pdt = -25200, -- PDT + nst = -12600, -- NST + ndt = -9000, -- NDT + aest = 36000, -- AEST + aedt = 39600, -- AEDT + nzst = 43200, -- NZST + nzdt = 46800, -- NZDT + hkt = 28800, -- HKT + wib = 25200, -- WIB + wit = 32400, -- WIT + ist = 7200, -- IST + idt = 10800, -- IDT + pkt = 18000, -- PKT + wita = 28800, -- WITA + kst = 32400, -- KST + jst = 32400, -- JST + wet = 0, -- WET + acst = 34200, -- ACST + acdt = 37800, -- ACDT + awst = 28800, -- AWST + msk = 10800, -- MSK + sst = -39600, -- SST +} diff --git a/modules/luci-lua-runtime/luasrc/template.lua b/modules/luci-lua-runtime/luasrc/template.lua new file mode 100644 index 0000000000..b7cc56e1cc --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/template.lua @@ -0,0 +1,184 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local util = require "luci.util" +local config = require "luci.config" +local tparser = require "luci.template.parser" + +local tostring, pairs, loadstring = tostring, pairs, loadstring +local setmetatable, loadfile = setmetatable, loadfile +local getfenv, setfenv, rawget = getfenv, setfenv, rawget +local assert, type, error = assert, type, error +local table, string, unpack = table, string, unpack + + +--- +--- bootstrap +--- +local _G = _G +local L = _G.L + +local http = _G.L.http + +local disp = require "luci.dispatcher" +local i18n = require "luci.i18n" +local xml = require "luci.xml" +local fs = require "nixio.fs" + + +--- LuCI template library. +module "luci.template" + +config.template = config.template or {} +viewdir = config.template.viewdir or util.libpath() .. "/view" + + +-- Define the namespace for template modules +context = {} --util.threadlocal() + +--- Render a certain template. +-- @param name Template name +-- @param scope Scope to assign to template (optional) +function render(name, scope) + return Template(name):render(scope or getfenv(2)) +end + +--- Render a template from a string. +-- @param template Template string +-- @param scope Scope to assign to template (optional) +function render_string(template, scope) + return Template(nil, template):render(scope or getfenv(2)) +end + + +-- Template class +Template = util.class() + +-- Shared template cache to store templates in to avoid unnecessary reloading +Template.cache = setmetatable({}, {__mode = "v"}) + + + +local function _ifattr(cond, key, val, noescape) + if cond then + local env = getfenv(3) + local scope = (type(env.self) == "table") and env.self + if type(val) == "table" then + if not next(val) then + return '' + else + val = util.serialize_json(val) + end + end + + val = tostring(val or + (type(env[key]) ~= "function" and env[key]) or + (scope and type(scope[key]) ~= "function" and scope[key]) or "") + + if noescape ~= true then + val = xml.pcdata(val) + end + + return string.format(' %s="%s"', tostring(key), val) + else + return '' + end +end + +context.viewns = setmetatable({ + include = function(name) + if fs.access(viewdir .. "/" .. name .. ".htm") then + Template(name):render(getfenv(2)) + else + L.include(name, getfenv(2)) + end + end; + translate = i18n.translate; + translatef = i18n.translatef; + export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end; + striptags = xml.striptags; + pcdata = xml.pcdata; + ifattr = function(...) return _ifattr(...) end; + attr = function(...) return _ifattr(true, ...) end; + url = disp.build_url; +}, {__index=function(tbl, key) + if key == "controller" then + return disp.build_url() + elseif key == "REQUEST_URI" then + return disp.build_url(unpack(disp.context.requestpath)) + elseif key == "FULL_REQUEST_URI" then + local url = { http:getenv("SCRIPT_NAME") or "", http:getenv("PATH_INFO") } + local query = http:getenv("QUERY_STRING") + if query and #query > 0 then + url[#url+1] = "?" + url[#url+1] = query + end + return table.concat(url, "") + elseif key == "token" then + return disp.context.authtoken + elseif key == "theme" then + return L.media and fs.basename(L.media) or tostring(L) + elseif key == "resource" then + return L.config.main.resourcebase + else + return rawget(tbl, key) or _G[key] or L[key] + end +end}) + + +-- Constructor - Reads and compiles the template on-demand +function Template.__init__(self, name, template) + if name then + self.template = self.cache[name] + self.name = name + else + self.name = "[string]" + end + + -- Create a new namespace for this template + self.viewns = context.viewns + + -- If we have a cached template, skip compiling and loading + if not self.template then + + -- Compile template + local err + local sourcefile + + if name then + sourcefile = viewdir .. "/" .. name .. ".htm" + self.template, _, err = tparser.parse(sourcefile) + else + sourcefile = "[string]" + self.template, _, err = tparser.parse_string(template) + end + + -- If we have no valid template throw error, otherwise cache the template + if not self.template then + error("Failed to load template '" .. self.name .. "'.\n" .. + "Error while parsing template '" .. sourcefile .. "':\n" .. + (err or "Unknown syntax error")) + elseif name then + self.cache[name] = self.template + end + end +end + + +-- Renders a template +function Template.render(self, scope) + scope = scope or getfenv(2) + + -- Put our predefined objects in the scope of the template + setfenv(self.template, setmetatable({}, {__index = + function(tbl, key) + return rawget(tbl, key) or self.viewns[key] or scope[key] + end})) + + -- Now finally render the thing + local stat, err = util.copcall(self.template) + if not stat then + error("Failed to execute template '" .. self.name .. "'.\n" .. + "A runtime error occurred: " .. tostring(err or "(nil)")) + end +end diff --git a/modules/luci-lua-runtime/luasrc/ucodebridge.lua b/modules/luci-lua-runtime/luasrc/ucodebridge.lua new file mode 100644 index 0000000000..fa4943dc99 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/ucodebridge.lua @@ -0,0 +1,52 @@ +-- Copyright 2022 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local coroutine, assert, error, type, require = coroutine, assert, error, type, require +local tmpl = require "luci.template" +local util = require "luci.util" +local http = require "luci.http" + + +--- LuCI ucode bridge library. +module "luci.ucodebridge" + +local function run(fn, ...) + local co = coroutine.create(fn) + local ok, ret + + while coroutine.status(co) ~= "dead" do + ok, ret = coroutine.resume(co, ...) + + if not ok then + error(ret) + end + end + + return ret +end + +function compile(path) + run(function(path) + return tmpl.Template(path) + end, path) +end + +function render(path, scope) + run(tmpl.render, path, scope) +end + +function call(modname, method, ...) + return run(function(module, method, ...) + local mod = require(modname) + local func = mod[method] + + assert(func ~= nil, + 'Cannot resolve function "' .. method .. '". Is it misspelled or local?') + + assert(type(func) == "function", + 'The symbol "' .. method .. '" does not refer to a function but data ' .. + 'of type "' .. type(func) .. '".') + + return func(...) + end, modname, method, ...) +end diff --git a/modules/luci-lua-runtime/luasrc/version.lua b/modules/luci-lua-runtime/luasrc/version.lua new file mode 100644 index 0000000000..8af2e80619 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/version.lua @@ -0,0 +1,9 @@ +-- Licensed to the public under the Apache License 2.0. + +module "luci.version" + +distname = "Host System" +distversion = "SDK" + +luciname = "LuCI" +luciversion = "SVN" diff --git a/modules/luci-lua-runtime/luasrc/view/empty_node_placeholder.htm b/modules/luci-lua-runtime/luasrc/view/empty_node_placeholder.htm new file mode 100644 index 0000000000..b7e276b960 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/view/empty_node_placeholder.htm @@ -0,0 +1,11 @@ +<%# + Copyright 2010 Jo-Philipp Wich + Copyright 2018 Daniel F. Dickinson + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> + +

Component not present.

+ +<%+footer%> diff --git a/modules/luci-lua-runtime/luasrc/view/indexer.htm b/modules/luci-lua-runtime/luasrc/view/indexer.htm new file mode 100644 index 0000000000..28fc3debc3 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/view/indexer.htm @@ -0,0 +1,7 @@ +<%# + Copyright 2008 Steven Barth + Copyright 2008 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + +<% include("themes/" .. theme .. "/indexer") %> \ No newline at end of file diff --git a/modules/luci-lua-runtime/luasrc/xml.lua b/modules/luci-lua-runtime/luasrc/xml.lua new file mode 100644 index 0000000000..30b37210bd --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/xml.lua @@ -0,0 +1,26 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local tparser = require "luci.template.parser" +local string = require "string" + +local tostring = tostring + +module "luci.xml" + +-- +-- String and data manipulation routines +-- + +function pcdata(value) + return value and tparser.pcdata(tostring(value)) +end + +function striptags(value) + return value and tparser.striptags(tostring(value)) +end + + +-- also register functions above in the central string class for convenience +string.pcdata = pcdata +string.striptags = striptags diff --git a/modules/luci-lua-runtime/luasrc/xml.luadoc b/modules/luci-lua-runtime/luasrc/xml.luadoc new file mode 100644 index 0000000000..58de533966 --- /dev/null +++ b/modules/luci-lua-runtime/luasrc/xml.luadoc @@ -0,0 +1,23 @@ +---[[ +LuCI utility functions. +]] +module "luci.xml" + +---[[ +Create valid XML PCDATA from given string. + +@class function +@name pcdata +@param value String value containing the data to escape +@return String value containing the escaped data +]] + +---[[ +Strip HTML tags from given string. + +@class function +@name striptags +@param value String containing the HTML text +@return String with HTML tags stripped of +]] + diff --git a/modules/luci-lua-runtime/src/Makefile b/modules/luci-lua-runtime/src/Makefile new file mode 100644 index 0000000000..dcce33dc8c --- /dev/null +++ b/modules/luci-lua-runtime/src/Makefile @@ -0,0 +1,26 @@ +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(FPIC) -DNDEBUG -c -o $@ $< + +contrib/lemon: contrib/lemon.c contrib/lempar.c + cc -o contrib/lemon $< + +plural_formula.c: plural_formula.y contrib/lemon + ./contrib/lemon -q $< + +template_lmo.c: plural_formula.c + +clean: + rm -f contrib/lemon parser.so plural_formula.c plural_formula.h *.o + +parser.so: template_parser.o template_utils.o template_lmo.o template_lualib.o plural_formula.o + $(CC) $(LDFLAGS) -shared -o $@ $^ + +version.lua: + ./mkversion.sh $@ $(LUCI_VERSION) "$(LUCI_GITBRANCH)" + +compile: parser.so version.lua + +install: compile + mkdir -p $(DESTDIR)/usr/lib/lua/luci/template + cp parser.so $(DESTDIR)/usr/lib/lua/luci/template/parser.so + cp version.lua $(DESTDIR)/usr/lib/lua/luci/version.lua diff --git a/modules/luci-lua-runtime/src/contrib/lemon.c b/modules/luci-lua-runtime/src/contrib/lemon.c new file mode 100644 index 0000000000..85e94f7007 --- /dev/null +++ b/modules/luci-lua-runtime/src/contrib/lemon.c @@ -0,0 +1,5040 @@ +/* +** This file contains all sources (including headers) to the LEMON +** LALR(1) parser generator. The sources have been combined into a +** single file to make it easy to include LEMON in the source tree +** and Makefile of another program. +** +** The author of this program disclaims copyright. +*/ +#include +#include +#include +#include +#include +#include + +#ifndef __WIN32__ +# if defined(_WIN32) || defined(WIN32) +# define __WIN32__ +# endif +#endif + +#ifdef __WIN32__ +#ifdef __cplusplus +extern "C" { +#endif +extern int access(const char *path, int mode); +#ifdef __cplusplus +} +#endif +#else +#include +#endif + +/* #define PRIVATE static */ +#define PRIVATE + +#ifdef TEST +#define MAXRHS 5 /* Set low to exercise exception code */ +#else +#define MAXRHS 1000 +#endif + +static int showPrecedenceConflict = 0; +static char *msort(char*,char**,int(*)(const char*,const char*)); + +/* +** Compilers are getting increasingly pedantic about type conversions +** as C evolves ever closer to Ada.... To work around the latest problems +** we have to define the following variant of strlen(). +*/ +#define lemonStrlen(X) ((int)strlen(X)) + +/* +** Compilers are starting to complain about the use of sprintf() and strcpy(), +** saying they are unsafe. So we define our own versions of those routines too. +** +** There are three routines here: lemon_sprintf(), lemon_vsprintf(), and +** lemon_addtext(). The first two are replacements for sprintf() and vsprintf(). +** The third is a helper routine for vsnprintf() that adds texts to the end of a +** buffer, making sure the buffer is always zero-terminated. +** +** The string formatter is a minimal subset of stdlib sprintf() supporting only +** a few simply conversions: +** +** %d +** %s +** %.*s +** +*/ +static void lemon_addtext( + char *zBuf, /* The buffer to which text is added */ + int *pnUsed, /* Slots of the buffer used so far */ + const char *zIn, /* Text to add */ + int nIn, /* Bytes of text to add. -1 to use strlen() */ + int iWidth /* Field width. Negative to left justify */ +){ + if( nIn<0 ) for(nIn=0; zIn[nIn]; nIn++){} + while( iWidth>nIn ){ zBuf[(*pnUsed)++] = ' '; iWidth--; } + if( nIn==0 ) return; + memcpy(&zBuf[*pnUsed], zIn, nIn); + *pnUsed += nIn; + while( (-iWidth)>nIn ){ zBuf[(*pnUsed)++] = ' '; iWidth++; } + zBuf[*pnUsed] = 0; +} +static int lemon_vsprintf(char *str, const char *zFormat, va_list ap){ + int i, j, k, c; + int nUsed = 0; + const char *z; + char zTemp[50]; + str[0] = 0; + for(i=j=0; (c = zFormat[i])!=0; i++){ + if( c=='%' ){ + int iWidth = 0; + lemon_addtext(str, &nUsed, &zFormat[j], i-j, 0); + c = zFormat[++i]; + if( isdigit(c) || (c=='-' && isdigit(zFormat[i+1])) ){ + if( c=='-' ) i++; + while( isdigit(zFormat[i]) ) iWidth = iWidth*10 + zFormat[i++] - '0'; + if( c=='-' ) iWidth = -iWidth; + c = zFormat[i]; + } + if( c=='d' ){ + int v = va_arg(ap, int); + if( v<0 ){ + lemon_addtext(str, &nUsed, "-", 1, iWidth); + v = -v; + }else if( v==0 ){ + lemon_addtext(str, &nUsed, "0", 1, iWidth); + } + k = 0; + while( v>0 ){ + k++; + zTemp[sizeof(zTemp)-k] = (v%10) + '0'; + v /= 10; + } + lemon_addtext(str, &nUsed, &zTemp[sizeof(zTemp)-k], k, iWidth); + }else if( c=='s' ){ + z = va_arg(ap, const char*); + lemon_addtext(str, &nUsed, z, -1, iWidth); + }else if( c=='.' && memcmp(&zFormat[i], ".*s", 3)==0 ){ + i += 2; + k = va_arg(ap, int); + z = va_arg(ap, const char*); + lemon_addtext(str, &nUsed, z, k, iWidth); + }else if( c=='%' ){ + lemon_addtext(str, &nUsed, "%", 1, 0); + }else{ + fprintf(stderr, "illegal format\n"); + exit(1); + } + j = i+1; + } + } + lemon_addtext(str, &nUsed, &zFormat[j], i-j, 0); + return nUsed; +} +static int lemon_sprintf(char *str, const char *format, ...){ + va_list ap; + int rc; + va_start(ap, format); + rc = lemon_vsprintf(str, format, ap); + va_end(ap); + return rc; +} +static void lemon_strcpy(char *dest, const char *src){ + while( (*(dest++) = *(src++))!=0 ){} +} +static void lemon_strcat(char *dest, const char *src){ + while( *dest ) dest++; + lemon_strcpy(dest, src); +} + + +/* a few forward declarations... */ +struct rule; +struct lemon; +struct action; + +static struct action *Action_new(void); +static struct action *Action_sort(struct action *); + +/********** From the file "build.h" ************************************/ +void FindRulePrecedences(); +void FindFirstSets(); +void FindStates(); +void FindLinks(); +void FindFollowSets(); +void FindActions(); + +/********* From the file "configlist.h" *********************************/ +void Configlist_init(void); +struct config *Configlist_add(struct rule *, int); +struct config *Configlist_addbasis(struct rule *, int); +void Configlist_closure(struct lemon *); +void Configlist_sort(void); +void Configlist_sortbasis(void); +struct config *Configlist_return(void); +struct config *Configlist_basis(void); +void Configlist_eat(struct config *); +void Configlist_reset(void); + +/********* From the file "error.h" ***************************************/ +void ErrorMsg(const char *, int,const char *, ...); + +/****** From the file "option.h" ******************************************/ +enum option_type { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR, + OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR}; +struct s_options { + enum option_type type; + const char *label; + char *arg; + const char *message; +}; +int OptInit(char**,struct s_options*,FILE*); +int OptNArgs(void); +char *OptArg(int); +void OptErr(int); +void OptPrint(void); + +/******** From the file "parse.h" *****************************************/ +void Parse(struct lemon *lemp); + +/********* From the file "plink.h" ***************************************/ +struct plink *Plink_new(void); +void Plink_add(struct plink **, struct config *); +void Plink_copy(struct plink **, struct plink *); +void Plink_delete(struct plink *); + +/********** From the file "report.h" *************************************/ +void Reprint(struct lemon *); +void ReportOutput(struct lemon *); +void ReportTable(struct lemon *, int); +void ReportHeader(struct lemon *); +void CompressTables(struct lemon *); +void ResortStates(struct lemon *); + +/********** From the file "set.h" ****************************************/ +void SetSize(int); /* All sets will be of size N */ +char *SetNew(void); /* A new set for element 0..N */ +void SetFree(char*); /* Deallocate a set */ +int SetAdd(char*,int); /* Add element to a set */ +int SetUnion(char *,char *); /* A <- A U B, thru element N */ +#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */ + +/********** From the file "struct.h" *************************************/ +/* +** Principal data structures for the LEMON parser generator. +*/ + +typedef enum {LEMON_FALSE=0, LEMON_TRUE} Boolean; + +/* Symbols (terminals and nonterminals) of the grammar are stored +** in the following: */ +enum symbol_type { + TERMINAL, + NONTERMINAL, + MULTITERMINAL +}; +enum e_assoc { + LEFT, + RIGHT, + NONE, + UNK +}; +struct symbol { + const char *name; /* Name of the symbol */ + int index; /* Index number for this symbol */ + enum symbol_type type; /* Symbols are all either TERMINALS or NTs */ + struct rule *rule; /* Linked list of rules of this (if an NT) */ + struct symbol *fallback; /* fallback token in case this token doesn't parse */ + int prec; /* Precedence if defined (-1 otherwise) */ + enum e_assoc assoc; /* Associativity if precedence is defined */ + char *firstset; /* First-set for all rules of this symbol */ + Boolean lambda; /* True if NT and can generate an empty string */ + int useCnt; /* Number of times used */ + char *destructor; /* Code which executes whenever this symbol is + ** popped from the stack during error processing */ + int destLineno; /* Line number for start of destructor */ + char *datatype; /* The data type of information held by this + ** object. Only used if type==NONTERMINAL */ + int dtnum; /* The data type number. In the parser, the value + ** stack is a union. The .yy%d element of this + ** union is the correct data type for this object */ + /* The following fields are used by MULTITERMINALs only */ + int nsubsym; /* Number of constituent symbols in the MULTI */ + struct symbol **subsym; /* Array of constituent symbols */ +}; + +/* Each production rule in the grammar is stored in the following +** structure. */ +struct rule { + struct symbol *lhs; /* Left-hand side of the rule */ + const char *lhsalias; /* Alias for the LHS (NULL if none) */ + int lhsStart; /* True if left-hand side is the start symbol */ + int ruleline; /* Line number for the rule */ + int nrhs; /* Number of RHS symbols */ + struct symbol **rhs; /* The RHS symbols */ + const char **rhsalias; /* An alias for each RHS symbol (NULL if none) */ + int line; /* Line number at which code begins */ + const char *code; /* The code executed when this rule is reduced */ + struct symbol *precsym; /* Precedence symbol for this rule */ + int index; /* An index number for this rule */ + Boolean canReduce; /* True if this rule is ever reduced */ + struct rule *nextlhs; /* Next rule with the same LHS */ + struct rule *next; /* Next rule in the global list */ +}; + +/* A configuration is a production rule of the grammar together with +** a mark (dot) showing how much of that rule has been processed so far. +** Configurations also contain a follow-set which is a list of terminal +** symbols which are allowed to immediately follow the end of the rule. +** Every configuration is recorded as an instance of the following: */ +enum cfgstatus { + COMPLETE, + INCOMPLETE +}; +struct config { + struct rule *rp; /* The rule upon which the configuration is based */ + int dot; /* The parse point */ + char *fws; /* Follow-set for this configuration only */ + struct plink *fplp; /* Follow-set forward propagation links */ + struct plink *bplp; /* Follow-set backwards propagation links */ + struct state *stp; /* Pointer to state which contains this */ + enum cfgstatus status; /* used during followset and shift computations */ + struct config *next; /* Next configuration in the state */ + struct config *bp; /* The next basis configuration */ +}; + +enum e_action { + SHIFT, + ACCEPT, + REDUCE, + ERROR, + SSCONFLICT, /* A shift/shift conflict */ + SRCONFLICT, /* Was a reduce, but part of a conflict */ + RRCONFLICT, /* Was a reduce, but part of a conflict */ + SH_RESOLVED, /* Was a shift. Precedence resolved conflict */ + RD_RESOLVED, /* Was reduce. Precedence resolved conflict */ + NOT_USED /* Deleted by compression */ +}; + +/* Every shift or reduce operation is stored as one of the following */ +struct action { + struct symbol *sp; /* The look-ahead symbol */ + enum e_action type; + union { + struct state *stp; /* The new state, if a shift */ + struct rule *rp; /* The rule, if a reduce */ + } x; + struct action *next; /* Next action for this state */ + struct action *collide; /* Next action with the same hash */ +}; + +/* Each state of the generated parser's finite state machine +** is encoded as an instance of the following structure. */ +struct state { + struct config *bp; /* The basis configurations for this state */ + struct config *cfp; /* All configurations in this set */ + int statenum; /* Sequential number for this state */ + struct action *ap; /* Array of actions for this state */ + int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */ + int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */ + int iDflt; /* Default action */ +}; +#define NO_OFFSET (-2147483647) + +/* A followset propagation link indicates that the contents of one +** configuration followset should be propagated to another whenever +** the first changes. */ +struct plink { + struct config *cfp; /* The configuration to which linked */ + struct plink *next; /* The next propagate link */ +}; + +/* The state vector for the entire parser generator is recorded as +** follows. (LEMON uses no global variables and makes little use of +** static variables. Fields in the following structure can be thought +** of as begin global variables in the program.) */ +struct lemon { + struct state **sorted; /* Table of states sorted by state number */ + struct rule *rule; /* List of all rules */ + int nstate; /* Number of states */ + int nrule; /* Number of rules */ + int nsymbol; /* Number of terminal and nonterminal symbols */ + int nterminal; /* Number of terminal symbols */ + struct symbol **symbols; /* Sorted array of pointers to symbols */ + int errorcnt; /* Number of errors */ + struct symbol *errsym; /* The error symbol */ + struct symbol *wildcard; /* Token that matches anything */ + char *name; /* Name of the generated parser */ + char *arg; /* Declaration of the 3th argument to parser */ + char *tokentype; /* Type of terminal symbols in the parser stack */ + char *vartype; /* The default type of non-terminal symbols */ + char *start; /* Name of the start symbol for the grammar */ + char *stacksize; /* Size of the parser stack */ + char *include; /* Code to put at the start of the C file */ + char *error; /* Code to execute when an error is seen */ + char *overflow; /* Code to execute on a stack overflow */ + char *failure; /* Code to execute on parser failure */ + char *accept; /* Code to execute when the parser excepts */ + char *extracode; /* Code appended to the generated file */ + char *tokendest; /* Code to execute to destroy token data */ + char *vardest; /* Code for the default non-terminal destructor */ + char *filename; /* Name of the input file */ + char *outname; /* Name of the current output file */ + char *tokenprefix; /* A prefix added to token names in the .h file */ + int nconflict; /* Number of parsing conflicts */ + int tablesize; /* Size of the parse tables */ + int basisflag; /* Print only basis configurations */ + int has_fallback; /* True if any %fallback is seen in the grammar */ + int nolinenosflag; /* True if #line statements should not be printed */ + char *argv0; /* Name of the program */ +}; + +#define MemoryCheck(X) if((X)==0){ \ + extern void memory_error(); \ + memory_error(); \ +} + +/**************** From the file "table.h" *********************************/ +/* +** All code in this file has been automatically generated +** from a specification in the file +** "table.q" +** by the associative array code building program "aagen". +** Do not edit this file! Instead, edit the specification +** file, then rerun aagen. +*/ +/* +** Code for processing tables in the LEMON parser generator. +*/ +/* Routines for handling a strings */ + +const char *Strsafe(const char *); + +void Strsafe_init(void); +int Strsafe_insert(const char *); +const char *Strsafe_find(const char *); + +/* Routines for handling symbols of the grammar */ + +struct symbol *Symbol_new(const char *); +int Symbolcmpp(const void *, const void *); +void Symbol_init(void); +int Symbol_insert(struct symbol *, const char *); +struct symbol *Symbol_find(const char *); +struct symbol *Symbol_Nth(int); +int Symbol_count(void); +struct symbol **Symbol_arrayof(void); + +/* Routines to manage the state table */ + +int Configcmp(const char *, const char *); +struct state *State_new(void); +void State_init(void); +int State_insert(struct state *, struct config *); +struct state *State_find(struct config *); +struct state **State_arrayof(/* */); + +/* Routines used for efficiency in Configlist_add */ + +void Configtable_init(void); +int Configtable_insert(struct config *); +struct config *Configtable_find(struct config *); +void Configtable_clear(int(*)(struct config *)); + +/****************** From the file "action.c" *******************************/ +/* +** Routines processing parser actions in the LEMON parser generator. +*/ + +/* Allocate a new parser action */ +static struct action *Action_new(void){ + static struct action *freelist = 0; + struct action *newaction; + + if( freelist==0 ){ + int i; + int amt = 100; + freelist = (struct action *)calloc(amt, sizeof(struct action)); + if( freelist==0 ){ + fprintf(stderr,"Unable to allocate memory for a new parser action."); + exit(1); + } + for(i=0; inext; + return newaction; +} + +/* Compare two actions for sorting purposes. Return negative, zero, or +** positive if the first action is less than, equal to, or greater than +** the first +*/ +static int actioncmp( + struct action *ap1, + struct action *ap2 +){ + int rc; + rc = ap1->sp->index - ap2->sp->index; + if( rc==0 ){ + rc = (int)ap1->type - (int)ap2->type; + } + if( rc==0 && ap1->type==REDUCE ){ + rc = ap1->x.rp->index - ap2->x.rp->index; + } + if( rc==0 ){ + rc = (int) (ap2 - ap1); + } + return rc; +} + +/* Sort parser actions */ +static struct action *Action_sort( + struct action *ap +){ + ap = (struct action *)msort((char *)ap,(char **)&ap->next, + (int(*)(const char*,const char*))actioncmp); + return ap; +} + +void Action_add( + struct action **app, + enum e_action type, + struct symbol *sp, + char *arg +){ + struct action *newaction; + newaction = Action_new(); + newaction->next = *app; + *app = newaction; + newaction->type = type; + newaction->sp = sp; + if( type==SHIFT ){ + newaction->x.stp = (struct state *)arg; + }else{ + newaction->x.rp = (struct rule *)arg; + } +} +/********************** New code to implement the "acttab" module ***********/ +/* +** This module implements routines use to construct the yy_action[] table. +*/ + +/* +** The state of the yy_action table under construction is an instance of +** the following structure. +** +** The yy_action table maps the pair (state_number, lookahead) into an +** action_number. The table is an array of integers pairs. The state_number +** determines an initial offset into the yy_action array. The lookahead +** value is then added to this initial offset to get an index X into the +** yy_action array. If the aAction[X].lookahead equals the value of the +** of the lookahead input, then the value of the action_number output is +** aAction[X].action. If the lookaheads do not match then the +** default action for the state_number is returned. +** +** All actions associated with a single state_number are first entered +** into aLookahead[] using multiple calls to acttab_action(). Then the +** actions for that single state_number are placed into the aAction[] +** array with a single call to acttab_insert(). The acttab_insert() call +** also resets the aLookahead[] array in preparation for the next +** state number. +*/ +struct lookahead_action { + int lookahead; /* Value of the lookahead token */ + int action; /* Action to take on the given lookahead */ +}; +typedef struct acttab acttab; +struct acttab { + int nAction; /* Number of used slots in aAction[] */ + int nActionAlloc; /* Slots allocated for aAction[] */ + struct lookahead_action + *aAction, /* The yy_action[] table under construction */ + *aLookahead; /* A single new transaction set */ + int mnLookahead; /* Minimum aLookahead[].lookahead */ + int mnAction; /* Action associated with mnLookahead */ + int mxLookahead; /* Maximum aLookahead[].lookahead */ + int nLookahead; /* Used slots in aLookahead[] */ + int nLookaheadAlloc; /* Slots allocated in aLookahead[] */ +}; + +/* Return the number of entries in the yy_action table */ +#define acttab_size(X) ((X)->nAction) + +/* The value for the N-th entry in yy_action */ +#define acttab_yyaction(X,N) ((X)->aAction[N].action) + +/* The value for the N-th entry in yy_lookahead */ +#define acttab_yylookahead(X,N) ((X)->aAction[N].lookahead) + +/* Free all memory associated with the given acttab */ +void acttab_free(acttab *p){ + free( p->aAction ); + free( p->aLookahead ); + free( p ); +} + +/* Allocate a new acttab structure */ +acttab *acttab_alloc(void){ + acttab *p = (acttab *) calloc( 1, sizeof(*p) ); + if( p==0 ){ + fprintf(stderr,"Unable to allocate memory for a new acttab."); + exit(1); + } + memset(p, 0, sizeof(*p)); + return p; +} + +/* Add a new action to the current transaction set. +** +** This routine is called once for each lookahead for a particular +** state. +*/ +void acttab_action(acttab *p, int lookahead, int action){ + if( p->nLookahead>=p->nLookaheadAlloc ){ + p->nLookaheadAlloc += 25; + p->aLookahead = (struct lookahead_action *) realloc( p->aLookahead, + sizeof(p->aLookahead[0])*p->nLookaheadAlloc ); + if( p->aLookahead==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + } + if( p->nLookahead==0 ){ + p->mxLookahead = lookahead; + p->mnLookahead = lookahead; + p->mnAction = action; + }else{ + if( p->mxLookaheadmxLookahead = lookahead; + if( p->mnLookahead>lookahead ){ + p->mnLookahead = lookahead; + p->mnAction = action; + } + } + p->aLookahead[p->nLookahead].lookahead = lookahead; + p->aLookahead[p->nLookahead].action = action; + p->nLookahead++; +} + +/* +** Add the transaction set built up with prior calls to acttab_action() +** into the current action table. Then reset the transaction set back +** to an empty set in preparation for a new round of acttab_action() calls. +** +** Return the offset into the action table of the new transaction. +*/ +int acttab_insert(acttab *p){ + int i, j, k, n; + assert( p->nLookahead>0 ); + + /* Make sure we have enough space to hold the expanded action table + ** in the worst case. The worst case occurs if the transaction set + ** must be appended to the current action table + */ + n = p->mxLookahead + 1; + if( p->nAction + n >= p->nActionAlloc ){ + int oldAlloc = p->nActionAlloc; + p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20; + p->aAction = (struct lookahead_action *) realloc( p->aAction, + sizeof(p->aAction[0])*p->nActionAlloc); + if( p->aAction==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + for(i=oldAlloc; inActionAlloc; i++){ + p->aAction[i].lookahead = -1; + p->aAction[i].action = -1; + } + } + + /* Scan the existing action table looking for an offset that is a + ** duplicate of the current transaction set. Fall out of the loop + ** if and when the duplicate is found. + ** + ** i is the index in p->aAction[] where p->mnLookahead is inserted. + */ + for(i=p->nAction-1; i>=0; i--){ + if( p->aAction[i].lookahead==p->mnLookahead ){ + /* All lookaheads and actions in the aLookahead[] transaction + ** must match against the candidate aAction[i] entry. */ + if( p->aAction[i].action!=p->mnAction ) continue; + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + if( k<0 || k>=p->nAction ) break; + if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break; + if( p->aLookahead[j].action!=p->aAction[k].action ) break; + } + if( jnLookahead ) continue; + + /* No possible lookahead value that is not in the aLookahead[] + ** transaction is allowed to match aAction[i] */ + n = 0; + for(j=0; jnAction; j++){ + if( p->aAction[j].lookahead<0 ) continue; + if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++; + } + if( n==p->nLookahead ){ + break; /* An exact match is found at offset i */ + } + } + } + + /* If no existing offsets exactly match the current transaction, find an + ** an empty offset in the aAction[] table in which we can add the + ** aLookahead[] transaction. + */ + if( i<0 ){ + /* Look for holes in the aAction[] table that fit the current + ** aLookahead[] transaction. Leave i set to the offset of the hole. + ** If no holes are found, i is left at p->nAction, which means the + ** transaction will be appended. */ + for(i=0; inActionAlloc - p->mxLookahead; i++){ + if( p->aAction[i].lookahead<0 ){ + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + if( k<0 ) break; + if( p->aAction[k].lookahead>=0 ) break; + } + if( jnLookahead ) continue; + for(j=0; jnAction; j++){ + if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break; + } + if( j==p->nAction ){ + break; /* Fits in empty slots */ + } + } + } + } + /* Insert transaction set at index i. */ + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + p->aAction[k] = p->aLookahead[j]; + if( k>=p->nAction ) p->nAction = k+1; + } + p->nLookahead = 0; + + /* Return the offset that is added to the lookahead in order to get the + ** index into yy_action of the action */ + return i - p->mnLookahead; +} + +/********************** From the file "build.c" *****************************/ +/* +** Routines to construction the finite state machine for the LEMON +** parser generator. +*/ + +/* Find a precedence symbol of every rule in the grammar. +** +** Those rules which have a precedence symbol coded in the input +** grammar using the "[symbol]" construct will already have the +** rp->precsym field filled. Other rules take as their precedence +** symbol the first RHS symbol with a defined precedence. If there +** are not RHS symbols with a defined precedence, the precedence +** symbol field is left blank. +*/ +void FindRulePrecedences(struct lemon *xp) +{ + struct rule *rp; + for(rp=xp->rule; rp; rp=rp->next){ + if( rp->precsym==0 ){ + int i, j; + for(i=0; inrhs && rp->precsym==0; i++){ + struct symbol *sp = rp->rhs[i]; + if( sp->type==MULTITERMINAL ){ + for(j=0; jnsubsym; j++){ + if( sp->subsym[j]->prec>=0 ){ + rp->precsym = sp->subsym[j]; + break; + } + } + }else if( sp->prec>=0 ){ + rp->precsym = rp->rhs[i]; + } + } + } + } + return; +} + +/* Find all nonterminals which will generate the empty string. +** Then go back and compute the first sets of every nonterminal. +** The first set is the set of all terminal symbols which can begin +** a string generated by that nonterminal. +*/ +void FindFirstSets(struct lemon *lemp) +{ + int i, j; + struct rule *rp; + int progress; + + for(i=0; insymbol; i++){ + lemp->symbols[i]->lambda = LEMON_FALSE; + } + for(i=lemp->nterminal; insymbol; i++){ + lemp->symbols[i]->firstset = SetNew(); + } + + /* First compute all lambdas */ + do{ + progress = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->lhs->lambda ) continue; + for(i=0; inrhs; i++){ + struct symbol *sp = rp->rhs[i]; + assert( sp->type==NONTERMINAL || sp->lambda==LEMON_FALSE ); + if( sp->lambda==LEMON_FALSE ) break; + } + if( i==rp->nrhs ){ + rp->lhs->lambda = LEMON_TRUE; + progress = 1; + } + } + }while( progress ); + + /* Now compute all first sets */ + do{ + struct symbol *s1, *s2; + progress = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + s1 = rp->lhs; + for(i=0; inrhs; i++){ + s2 = rp->rhs[i]; + if( s2->type==TERMINAL ){ + progress += SetAdd(s1->firstset,s2->index); + break; + }else if( s2->type==MULTITERMINAL ){ + for(j=0; jnsubsym; j++){ + progress += SetAdd(s1->firstset,s2->subsym[j]->index); + } + break; + }else if( s1==s2 ){ + if( s1->lambda==LEMON_FALSE ) break; + }else{ + progress += SetUnion(s1->firstset,s2->firstset); + if( s2->lambda==LEMON_FALSE ) break; + } + } + } + }while( progress ); + return; +} + +/* Compute all LR(0) states for the grammar. Links +** are added to between some states so that the LR(1) follow sets +** can be computed later. +*/ +PRIVATE struct state *getstate(struct lemon *); /* forward reference */ +void FindStates(struct lemon *lemp) +{ + struct symbol *sp; + struct rule *rp; + + Configlist_init(); + + /* Find the start symbol */ + if( lemp->start ){ + sp = Symbol_find(lemp->start); + if( sp==0 ){ + ErrorMsg(lemp->filename,0, +"The specified start symbol \"%s\" is not \ +in a nonterminal of the grammar. \"%s\" will be used as the start \ +symbol instead.",lemp->start,lemp->rule->lhs->name); + lemp->errorcnt++; + sp = lemp->rule->lhs; + } + }else{ + sp = lemp->rule->lhs; + } + + /* Make sure the start symbol doesn't occur on the right-hand side of + ** any rule. Report an error if it does. (YACC would generate a new + ** start symbol in this case.) */ + for(rp=lemp->rule; rp; rp=rp->next){ + int i; + for(i=0; inrhs; i++){ + if( rp->rhs[i]==sp ){ /* FIX ME: Deal with multiterminals */ + ErrorMsg(lemp->filename,0, +"The start symbol \"%s\" occurs on the \ +right-hand side of a rule. This will result in a parser which \ +does not work properly.",sp->name); + lemp->errorcnt++; + } + } + } + + /* The basis configuration set for the first state + ** is all rules which have the start symbol as their + ** left-hand side */ + for(rp=sp->rule; rp; rp=rp->nextlhs){ + struct config *newcfp; + rp->lhsStart = 1; + newcfp = Configlist_addbasis(rp,0); + SetAdd(newcfp->fws,0); + } + + /* Compute the first state. All other states will be + ** computed automatically during the computation of the first one. + ** The returned pointer to the first state is not used. */ + (void)getstate(lemp); + return; +} + +/* Return a pointer to a state which is described by the configuration +** list which has been built from calls to Configlist_add. +*/ +PRIVATE void buildshifts(struct lemon *, struct state *); /* Forwd ref */ +PRIVATE struct state *getstate(struct lemon *lemp) +{ + struct config *cfp, *bp; + struct state *stp; + + /* Extract the sorted basis of the new state. The basis was constructed + ** by prior calls to "Configlist_addbasis()". */ + Configlist_sortbasis(); + bp = Configlist_basis(); + + /* Get a state with the same basis */ + stp = State_find(bp); + if( stp ){ + /* A state with the same basis already exists! Copy all the follow-set + ** propagation links from the state under construction into the + ** preexisting state, then return a pointer to the preexisting state */ + struct config *x, *y; + for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){ + Plink_copy(&y->bplp,x->bplp); + Plink_delete(x->fplp); + x->fplp = x->bplp = 0; + } + cfp = Configlist_return(); + Configlist_eat(cfp); + }else{ + /* This really is a new state. Construct all the details */ + Configlist_closure(lemp); /* Compute the configuration closure */ + Configlist_sort(); /* Sort the configuration closure */ + cfp = Configlist_return(); /* Get a pointer to the config list */ + stp = State_new(); /* A new state structure */ + MemoryCheck(stp); + stp->bp = bp; /* Remember the configuration basis */ + stp->cfp = cfp; /* Remember the configuration closure */ + stp->statenum = lemp->nstate++; /* Every state gets a sequence number */ + stp->ap = 0; /* No actions, yet. */ + State_insert(stp,stp->bp); /* Add to the state table */ + buildshifts(lemp,stp); /* Recursively compute successor states */ + } + return stp; +} + +/* +** Return true if two symbols are the same. +*/ +int same_symbol(struct symbol *a, struct symbol *b) +{ + int i; + if( a==b ) return 1; + if( a->type!=MULTITERMINAL ) return 0; + if( b->type!=MULTITERMINAL ) return 0; + if( a->nsubsym!=b->nsubsym ) return 0; + for(i=0; insubsym; i++){ + if( a->subsym[i]!=b->subsym[i] ) return 0; + } + return 1; +} + +/* Construct all successor states to the given state. A "successor" +** state is any state which can be reached by a shift action. +*/ +PRIVATE void buildshifts(struct lemon *lemp, struct state *stp) +{ + struct config *cfp; /* For looping thru the config closure of "stp" */ + struct config *bcfp; /* For the inner loop on config closure of "stp" */ + struct config *newcfg; /* */ + struct symbol *sp; /* Symbol following the dot in configuration "cfp" */ + struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */ + struct state *newstp; /* A pointer to a successor state */ + + /* Each configuration becomes complete after it contibutes to a successor + ** state. Initially, all configurations are incomplete */ + for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE; + + /* Loop through all configurations of the state "stp" */ + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */ + if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */ + Configlist_reset(); /* Reset the new config set */ + sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */ + + /* For every configuration in the state "stp" which has the symbol "sp" + ** following its dot, add the same configuration to the basis set under + ** construction but with the dot shifted one symbol to the right. */ + for(bcfp=cfp; bcfp; bcfp=bcfp->next){ + if( bcfp->status==COMPLETE ) continue; /* Already used */ + if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */ + bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */ + if( !same_symbol(bsp,sp) ) continue; /* Must be same as for "cfp" */ + bcfp->status = COMPLETE; /* Mark this config as used */ + newcfg = Configlist_addbasis(bcfp->rp,bcfp->dot+1); + Plink_add(&newcfg->bplp,bcfp); + } + + /* Get a pointer to the state described by the basis configuration set + ** constructed in the preceding loop */ + newstp = getstate(lemp); + + /* The state "newstp" is reached from the state "stp" by a shift action + ** on the symbol "sp" */ + if( sp->type==MULTITERMINAL ){ + int i; + for(i=0; insubsym; i++){ + Action_add(&stp->ap,SHIFT,sp->subsym[i],(char*)newstp); + } + }else{ + Action_add(&stp->ap,SHIFT,sp,(char *)newstp); + } + } +} + +/* +** Construct the propagation links +*/ +void FindLinks(struct lemon *lemp) +{ + int i; + struct config *cfp, *other; + struct state *stp; + struct plink *plp; + + /* Housekeeping detail: + ** Add to every propagate link a pointer back to the state to + ** which the link is attached. */ + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + cfp->stp = stp; + } + } + + /* Convert all backlinks into forward links. Only the forward + ** links are used in the follow-set computation. */ + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + for(plp=cfp->bplp; plp; plp=plp->next){ + other = plp->cfp; + Plink_add(&other->fplp,cfp); + } + } + } +} + +/* Compute all followsets. +** +** A followset is the set of all symbols which can come immediately +** after a configuration. +*/ +void FindFollowSets(struct lemon *lemp) +{ + int i; + struct config *cfp; + struct plink *plp; + int progress; + int change; + + for(i=0; instate; i++){ + for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ + cfp->status = INCOMPLETE; + } + } + + do{ + progress = 0; + for(i=0; instate; i++){ + for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ + if( cfp->status==COMPLETE ) continue; + for(plp=cfp->fplp; plp; plp=plp->next){ + change = SetUnion(plp->cfp->fws,cfp->fws); + if( change ){ + plp->cfp->status = INCOMPLETE; + progress = 1; + } + } + cfp->status = COMPLETE; + } + } + }while( progress ); +} + +static int resolve_conflict(struct action *,struct action *); + +/* Compute the reduce actions, and resolve conflicts. +*/ +void FindActions(struct lemon *lemp) +{ + int i,j; + struct config *cfp; + struct state *stp; + struct symbol *sp; + struct rule *rp; + + /* Add all of the reduce actions + ** A reduce action is added for each element of the followset of + ** a configuration which has its dot at the extreme right. + */ + for(i=0; instate; i++){ /* Loop over all states */ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */ + if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */ + for(j=0; jnterminal; j++){ + if( SetFind(cfp->fws,j) ){ + /* Add a reduce action to the state "stp" which will reduce by the + ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */ + Action_add(&stp->ap,REDUCE,lemp->symbols[j],(char *)cfp->rp); + } + } + } + } + } + + /* Add the accepting token */ + if( lemp->start ){ + sp = Symbol_find(lemp->start); + if( sp==0 ) sp = lemp->rule->lhs; + }else{ + sp = lemp->rule->lhs; + } + /* Add to the first state (which is always the starting state of the + ** finite state machine) an action to ACCEPT if the lookahead is the + ** start nonterminal. */ + Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0); + + /* Resolve conflicts */ + for(i=0; instate; i++){ + struct action *ap, *nap; + struct state *stp; + stp = lemp->sorted[i]; + /* assert( stp->ap ); */ + stp->ap = Action_sort(stp->ap); + for(ap=stp->ap; ap && ap->next; ap=ap->next){ + for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){ + /* The two actions "ap" and "nap" have the same lookahead. + ** Figure out which one should be used */ + lemp->nconflict += resolve_conflict(ap,nap); + } + } + } + + /* Report an error for each rule that can never be reduced. */ + for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = LEMON_FALSE; + for(i=0; instate; i++){ + struct action *ap; + for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){ + if( ap->type==REDUCE ) ap->x.rp->canReduce = LEMON_TRUE; + } + } + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->canReduce ) continue; + ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n"); + lemp->errorcnt++; + } +} + +/* Resolve a conflict between the two given actions. If the +** conflict can't be resolved, return non-zero. +** +** NO LONGER TRUE: +** To resolve a conflict, first look to see if either action +** is on an error rule. In that case, take the action which +** is not associated with the error rule. If neither or both +** actions are associated with an error rule, then try to +** use precedence to resolve the conflict. +** +** If either action is a SHIFT, then it must be apx. This +** function won't work if apx->type==REDUCE and apy->type==SHIFT. +*/ +static int resolve_conflict( + struct action *apx, + struct action *apy +){ + struct symbol *spx, *spy; + int errcnt = 0; + assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */ + if( apx->type==SHIFT && apy->type==SHIFT ){ + apy->type = SSCONFLICT; + errcnt++; + } + if( apx->type==SHIFT && apy->type==REDUCE ){ + spx = apx->sp; + spy = apy->x.rp->precsym; + if( spy==0 || spx->prec<0 || spy->prec<0 ){ + /* Not enough precedence information. */ + apy->type = SRCONFLICT; + errcnt++; + }else if( spx->prec>spy->prec ){ /* higher precedence wins */ + apy->type = RD_RESOLVED; + }else if( spx->precprec ){ + apx->type = SH_RESOLVED; + }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */ + apy->type = RD_RESOLVED; /* associativity */ + }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */ + apx->type = SH_RESOLVED; + }else{ + assert( spx->prec==spy->prec && spx->assoc==NONE ); + apx->type = ERROR; + } + }else if( apx->type==REDUCE && apy->type==REDUCE ){ + spx = apx->x.rp->precsym; + spy = apy->x.rp->precsym; + if( spx==0 || spy==0 || spx->prec<0 || + spy->prec<0 || spx->prec==spy->prec ){ + apy->type = RRCONFLICT; + errcnt++; + }else if( spx->prec>spy->prec ){ + apy->type = RD_RESOLVED; + }else if( spx->precprec ){ + apx->type = RD_RESOLVED; + } + }else{ + assert( + apx->type==SH_RESOLVED || + apx->type==RD_RESOLVED || + apx->type==SSCONFLICT || + apx->type==SRCONFLICT || + apx->type==RRCONFLICT || + apy->type==SH_RESOLVED || + apy->type==RD_RESOLVED || + apy->type==SSCONFLICT || + apy->type==SRCONFLICT || + apy->type==RRCONFLICT + ); + /* The REDUCE/SHIFT case cannot happen because SHIFTs come before + ** REDUCEs on the list. If we reach this point it must be because + ** the parser conflict had already been resolved. */ + } + return errcnt; +} +/********************* From the file "configlist.c" *************************/ +/* +** Routines to processing a configuration list and building a state +** in the LEMON parser generator. +*/ + +static struct config *freelist = 0; /* List of free configurations */ +static struct config *current = 0; /* Top of list of configurations */ +static struct config **currentend = 0; /* Last on list of configs */ +static struct config *basis = 0; /* Top of list of basis configs */ +static struct config **basisend = 0; /* End of list of basis configs */ + +/* Return a pointer to a new configuration */ +PRIVATE struct config *newconfig(){ + struct config *newcfg; + if( freelist==0 ){ + int i; + int amt = 3; + freelist = (struct config *)calloc( amt, sizeof(struct config) ); + if( freelist==0 ){ + fprintf(stderr,"Unable to allocate memory for a new configuration."); + exit(1); + } + for(i=0; inext; + return newcfg; +} + +/* The configuration "old" is no longer used */ +PRIVATE void deleteconfig(struct config *old) +{ + old->next = freelist; + freelist = old; +} + +/* Initialized the configuration list builder */ +void Configlist_init(){ + current = 0; + currentend = ¤t; + basis = 0; + basisend = &basis; + Configtable_init(); + return; +} + +/* Initialized the configuration list builder */ +void Configlist_reset(){ + current = 0; + currentend = ¤t; + basis = 0; + basisend = &basis; + Configtable_clear(0); + return; +} + +/* Add another configuration to the configuration list */ +struct config *Configlist_add( + struct rule *rp, /* The rule */ + int dot /* Index into the RHS of the rule where the dot goes */ +){ + struct config *cfp, model; + + assert( currentend!=0 ); + model.rp = rp; + model.dot = dot; + cfp = Configtable_find(&model); + if( cfp==0 ){ + cfp = newconfig(); + cfp->rp = rp; + cfp->dot = dot; + cfp->fws = SetNew(); + cfp->stp = 0; + cfp->fplp = cfp->bplp = 0; + cfp->next = 0; + cfp->bp = 0; + *currentend = cfp; + currentend = &cfp->next; + Configtable_insert(cfp); + } + return cfp; +} + +/* Add a basis configuration to the configuration list */ +struct config *Configlist_addbasis(struct rule *rp, int dot) +{ + struct config *cfp, model; + + assert( basisend!=0 ); + assert( currentend!=0 ); + model.rp = rp; + model.dot = dot; + cfp = Configtable_find(&model); + if( cfp==0 ){ + cfp = newconfig(); + cfp->rp = rp; + cfp->dot = dot; + cfp->fws = SetNew(); + cfp->stp = 0; + cfp->fplp = cfp->bplp = 0; + cfp->next = 0; + cfp->bp = 0; + *currentend = cfp; + currentend = &cfp->next; + *basisend = cfp; + basisend = &cfp->bp; + Configtable_insert(cfp); + } + return cfp; +} + +/* Compute the closure of the configuration list */ +void Configlist_closure(struct lemon *lemp) +{ + struct config *cfp, *newcfp; + struct rule *rp, *newrp; + struct symbol *sp, *xsp; + int i, dot; + + assert( currentend!=0 ); + for(cfp=current; cfp; cfp=cfp->next){ + rp = cfp->rp; + dot = cfp->dot; + if( dot>=rp->nrhs ) continue; + sp = rp->rhs[dot]; + if( sp->type==NONTERMINAL ){ + if( sp->rule==0 && sp!=lemp->errsym ){ + ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.", + sp->name); + lemp->errorcnt++; + } + for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){ + newcfp = Configlist_add(newrp,0); + for(i=dot+1; inrhs; i++){ + xsp = rp->rhs[i]; + if( xsp->type==TERMINAL ){ + SetAdd(newcfp->fws,xsp->index); + break; + }else if( xsp->type==MULTITERMINAL ){ + int k; + for(k=0; knsubsym; k++){ + SetAdd(newcfp->fws, xsp->subsym[k]->index); + } + break; + }else{ + SetUnion(newcfp->fws,xsp->firstset); + if( xsp->lambda==LEMON_FALSE ) break; + } + } + if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp); + } + } + } + return; +} + +/* Sort the configuration list */ +void Configlist_sort(){ + current = (struct config *)msort((char *)current,(char **)&(current->next),Configcmp); + currentend = 0; + return; +} + +/* Sort the basis configuration list */ +void Configlist_sortbasis(){ + basis = (struct config *)msort((char *)current,(char **)&(current->bp),Configcmp); + basisend = 0; + return; +} + +/* Return a pointer to the head of the configuration list and +** reset the list */ +struct config *Configlist_return(){ + struct config *old; + old = current; + current = 0; + currentend = 0; + return old; +} + +/* Return a pointer to the head of the configuration list and +** reset the list */ +struct config *Configlist_basis(){ + struct config *old; + old = basis; + basis = 0; + basisend = 0; + return old; +} + +/* Free all elements of the given configuration list */ +void Configlist_eat(struct config *cfp) +{ + struct config *nextcfp; + for(; cfp; cfp=nextcfp){ + nextcfp = cfp->next; + assert( cfp->fplp==0 ); + assert( cfp->bplp==0 ); + if( cfp->fws ) SetFree(cfp->fws); + deleteconfig(cfp); + } + return; +} +/***************** From the file "error.c" *********************************/ +/* +** Code for printing error message. +*/ + +void ErrorMsg(const char *filename, int lineno, const char *format, ...){ + va_list ap; + fprintf(stderr, "%s:%d: ", filename, lineno); + va_start(ap, format); + vfprintf(stderr,format,ap); + va_end(ap); + fprintf(stderr, "\n"); +} +/**************** From the file "main.c" ************************************/ +/* +** Main program file for the LEMON parser generator. +*/ + +/* Report an out-of-memory condition and abort. This function +** is used mostly by the "MemoryCheck" macro in struct.h +*/ +void memory_error(){ + fprintf(stderr,"Out of memory. Aborting...\n"); + exit(1); +} + +static int nDefine = 0; /* Number of -D options on the command line */ +static char **azDefine = 0; /* Name of the -D macros */ + +/* This routine is called with the argument to each -D command-line option. +** Add the macro defined to the azDefine array. +*/ +static void handle_D_option(char *z){ + char **paz; + nDefine++; + azDefine = (char **) realloc(azDefine, sizeof(azDefine[0])*nDefine); + if( azDefine==0 ){ + fprintf(stderr,"out of memory\n"); + exit(1); + } + paz = &azDefine[nDefine-1]; + *paz = (char *) malloc( lemonStrlen(z)+1 ); + if( *paz==0 ){ + fprintf(stderr,"out of memory\n"); + exit(1); + } + lemon_strcpy(*paz, z); + for(z=*paz; *z && *z!='='; z++){} + *z = 0; +} + +static char *user_templatename = NULL; +static void handle_T_option(char *z){ + user_templatename = (char *) malloc( lemonStrlen(z)+1 ); + if( user_templatename==0 ){ + memory_error(); + } + lemon_strcpy(user_templatename, z); +} + +/* The main program. Parse the command line and do it... */ +int main(int argc, char **argv) +{ + static int version = 0; + static int rpflag = 0; + static int basisflag = 0; + static int compress = 0; + static int quiet = 0; + static int statistics = 0; + static int mhflag = 0; + static int nolinenosflag = 0; + static int noResort = 0; + static struct s_options options[] = { + {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."}, + {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."}, + {OPT_FSTR, "D", (char*)handle_D_option, "Define an %ifdef macro."}, + {OPT_FSTR, "T", (char*)handle_T_option, "Specify a template file."}, + {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."}, + {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file."}, + {OPT_FLAG, "l", (char*)&nolinenosflag, "Do not print #line statements."}, + {OPT_FLAG, "p", (char*)&showPrecedenceConflict, + "Show conflicts resolved by precedence rules"}, + {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."}, + {OPT_FLAG, "r", (char*)&noResort, "Do not sort or renumber states"}, + {OPT_FLAG, "s", (char*)&statistics, + "Print parser stats to standard output."}, + {OPT_FLAG, "x", (char*)&version, "Print the version number."}, + {OPT_FLAG,0,0,0} + }; + int i; + int exitcode; + struct lemon lem; + + OptInit(argv,options,stderr); + if( version ){ + printf("Lemon version 1.0\n"); + exit(0); + } + if( OptNArgs()!=1 ){ + fprintf(stderr,"Exactly one filename argument is required.\n"); + exit(1); + } + memset(&lem, 0, sizeof(lem)); + lem.errorcnt = 0; + + /* Initialize the machine */ + Strsafe_init(); + Symbol_init(); + State_init(); + lem.argv0 = argv[0]; + lem.filename = OptArg(0); + lem.basisflag = basisflag; + lem.nolinenosflag = nolinenosflag; + Symbol_new("$"); + lem.errsym = Symbol_new("error"); + lem.errsym->useCnt = 0; + + /* Parse the input file */ + Parse(&lem); + if( lem.errorcnt ) exit(lem.errorcnt); + if( lem.nrule==0 ){ + fprintf(stderr,"Empty grammar.\n"); + exit(1); + } + + /* Count and index the symbols of the grammar */ + Symbol_new("{default}"); + lem.nsymbol = Symbol_count(); + lem.symbols = Symbol_arrayof(); + for(i=0; iindex = i; + qsort(lem.symbols,lem.nsymbol,sizeof(struct symbol*), Symbolcmpp); + for(i=0; iindex = i; + while( lem.symbols[i-1]->type==MULTITERMINAL ){ i--; } + assert( strcmp(lem.symbols[i-1]->name,"{default}")==0 ); + lem.nsymbol = i - 1; + for(i=1; isupper(lem.symbols[i]->name[0]); i++); + lem.nterminal = i; + + /* Generate a reprint of the grammar, if requested on the command line */ + if( rpflag ){ + Reprint(&lem); + }else{ + /* Initialize the size for all follow and first sets */ + SetSize(lem.nterminal+1); + + /* Find the precedence for every production rule (that has one) */ + FindRulePrecedences(&lem); + + /* Compute the lambda-nonterminals and the first-sets for every + ** nonterminal */ + FindFirstSets(&lem); + + /* Compute all LR(0) states. Also record follow-set propagation + ** links so that the follow-set can be computed later */ + lem.nstate = 0; + FindStates(&lem); + lem.sorted = State_arrayof(); + + /* Tie up loose ends on the propagation links */ + FindLinks(&lem); + + /* Compute the follow set of every reducible configuration */ + FindFollowSets(&lem); + + /* Compute the action tables */ + FindActions(&lem); + + /* Compress the action tables */ + if( compress==0 ) CompressTables(&lem); + + /* Reorder and renumber the states so that states with fewer choices + ** occur at the end. This is an optimization that helps make the + ** generated parser tables smaller. */ + if( noResort==0 ) ResortStates(&lem); + + /* Generate a report of the parser generated. (the "y.output" file) */ + if( !quiet ) ReportOutput(&lem); + + /* Generate the source code for the parser */ + ReportTable(&lem, mhflag); + + /* Produce a header file for use by the scanner. (This step is + ** omitted if the "-m" option is used because makeheaders will + ** generate the file for us.) */ + if( !mhflag ) ReportHeader(&lem); + } + if( statistics ){ + printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n", + lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule); + printf(" %d states, %d parser table entries, %d conflicts\n", + lem.nstate, lem.tablesize, lem.nconflict); + } + if( lem.nconflict > 0 ){ + fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict); + } + + /* return 0 on success, 1 on failure. */ + exitcode = ((lem.errorcnt > 0) || (lem.nconflict > 0)) ? 1 : 0; + exit(exitcode); + return (exitcode); +} +/******************** From the file "msort.c" *******************************/ +/* +** A generic merge-sort program. +** +** USAGE: +** Let "ptr" be a pointer to some structure which is at the head of +** a null-terminated list. Then to sort the list call: +** +** ptr = msort(ptr,&(ptr->next),cmpfnc); +** +** In the above, "cmpfnc" is a pointer to a function which compares +** two instances of the structure and returns an integer, as in +** strcmp. The second argument is a pointer to the pointer to the +** second element of the linked list. This address is used to compute +** the offset to the "next" field within the structure. The offset to +** the "next" field must be constant for all structures in the list. +** +** The function returns a new pointer which is the head of the list +** after sorting. +** +** ALGORITHM: +** Merge-sort. +*/ + +/* +** Return a pointer to the next structure in the linked list. +*/ +#define NEXT(A) (*(char**)(((char*)A)+offset)) + +/* +** Inputs: +** a: A sorted, null-terminated linked list. (May be null). +** b: A sorted, null-terminated linked list. (May be null). +** cmp: A pointer to the comparison function. +** offset: Offset in the structure to the "next" field. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** of both a and b. +** +** Side effects: +** The "next" pointers for elements in the lists a and b are +** changed. +*/ +static char *merge( + char *a, + char *b, + int (*cmp)(const char*,const char*), + int offset +){ + char *ptr, *head; + + if( a==0 ){ + head = b; + }else if( b==0 ){ + head = a; + }else{ + if( (*cmp)(a,b)<=0 ){ + ptr = a; + a = NEXT(a); + }else{ + ptr = b; + b = NEXT(b); + } + head = ptr; + while( a && b ){ + if( (*cmp)(a,b)<=0 ){ + NEXT(ptr) = a; + ptr = a; + a = NEXT(a); + }else{ + NEXT(ptr) = b; + ptr = b; + b = NEXT(b); + } + } + if( a ) NEXT(ptr) = a; + else NEXT(ptr) = b; + } + return head; +} + +/* +** Inputs: +** list: Pointer to a singly-linked list of structures. +** next: Pointer to pointer to the second element of the list. +** cmp: A comparison function. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** orginally in list. +** +** Side effects: +** The "next" pointers for elements in list are changed. +*/ +#define LISTSIZE 30 +static char *msort( + char *list, + char **next, + int (*cmp)(const char*,const char*) +){ + unsigned long offset; + char *ep; + char *set[LISTSIZE]; + int i; + offset = (unsigned long)next - (unsigned long)list; + for(i=0; istate = WAITING_FOR_DECL_KEYWORD; + }else if( islower(x[0]) ){ + psp->lhs = Symbol_new(x); + psp->nrhs = 0; + psp->lhsalias = 0; + psp->state = WAITING_FOR_ARROW; + }else if( x[0]=='{' ){ + if( psp->prevrule==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"There is no prior rule upon which to attach the code \ +fragment which begins on this line."); + psp->errorcnt++; + }else if( psp->prevrule->code!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"Code fragment beginning on this line is not the first \ +to follow the previous rule."); + psp->errorcnt++; + }else{ + psp->prevrule->line = psp->tokenlineno; + psp->prevrule->code = &x[1]; + } + }else if( x[0]=='[' ){ + psp->state = PRECEDENCE_MARK_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Token \"%s\" should be either \"%%\" or a nonterminal name.", + x); + psp->errorcnt++; + } + break; + case PRECEDENCE_MARK_1: + if( !isupper(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "The precedence symbol must be a terminal."); + psp->errorcnt++; + }else if( psp->prevrule==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "There is no prior rule to assign precedence \"[%s]\".",x); + psp->errorcnt++; + }else if( psp->prevrule->precsym!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"Precedence mark on this line is not the first \ +to follow the previous rule."); + psp->errorcnt++; + }else{ + psp->prevrule->precsym = Symbol_new(x); + } + psp->state = PRECEDENCE_MARK_2; + break; + case PRECEDENCE_MARK_2: + if( x[0]!=']' ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \"]\" on precedence mark."); + psp->errorcnt++; + } + psp->state = WAITING_FOR_DECL_OR_RULE; + break; + case WAITING_FOR_ARROW: + if( x[0]==':' && x[1]==':' && x[2]=='=' ){ + psp->state = IN_RHS; + }else if( x[0]=='(' ){ + psp->state = LHS_ALIAS_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Expected to see a \":\" following the LHS symbol \"%s\".", + psp->lhs->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_1: + if( isalpha(x[0]) ){ + psp->lhsalias = x; + psp->state = LHS_ALIAS_2; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "\"%s\" is not a valid alias for the LHS \"%s\"\n", + x,psp->lhs->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_2: + if( x[0]==')' ){ + psp->state = LHS_ALIAS_3; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_3: + if( x[0]==':' && x[1]==':' && x[2]=='=' ){ + psp->state = IN_RHS; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \"->\" following: \"%s(%s)\".", + psp->lhs->name,psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case IN_RHS: + if( x[0]=='.' ){ + struct rule *rp; + rp = (struct rule *)calloc( sizeof(struct rule) + + sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs, 1); + if( rp==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Can't allocate enough memory for this rule."); + psp->errorcnt++; + psp->prevrule = 0; + }else{ + int i; + rp->ruleline = psp->tokenlineno; + rp->rhs = (struct symbol**)&rp[1]; + rp->rhsalias = (const char**)&(rp->rhs[psp->nrhs]); + for(i=0; inrhs; i++){ + rp->rhs[i] = psp->rhs[i]; + rp->rhsalias[i] = psp->alias[i]; + } + rp->lhs = psp->lhs; + rp->lhsalias = psp->lhsalias; + rp->nrhs = psp->nrhs; + rp->code = 0; + rp->precsym = 0; + rp->index = psp->gp->nrule++; + rp->nextlhs = rp->lhs->rule; + rp->lhs->rule = rp; + rp->next = 0; + if( psp->firstrule==0 ){ + psp->firstrule = psp->lastrule = rp; + }else{ + psp->lastrule->next = rp; + psp->lastrule = rp; + } + psp->prevrule = rp; + } + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( isalpha(x[0]) ){ + if( psp->nrhs>=MAXRHS ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Too many symbols on RHS of rule beginning at \"%s\".", + x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + }else{ + psp->rhs[psp->nrhs] = Symbol_new(x); + psp->alias[psp->nrhs] = 0; + psp->nrhs++; + } + }else if( (x[0]=='|' || x[0]=='/') && psp->nrhs>0 ){ + struct symbol *msp = psp->rhs[psp->nrhs-1]; + if( msp->type!=MULTITERMINAL ){ + struct symbol *origsp = msp; + msp = (struct symbol *) calloc(1,sizeof(*msp)); + memset(msp, 0, sizeof(*msp)); + msp->type = MULTITERMINAL; + msp->nsubsym = 1; + msp->subsym = (struct symbol **) calloc(1,sizeof(struct symbol*)); + msp->subsym[0] = origsp; + msp->name = origsp->name; + psp->rhs[psp->nrhs-1] = msp; + } + msp->nsubsym++; + msp->subsym = (struct symbol **) realloc(msp->subsym, + sizeof(struct symbol*)*msp->nsubsym); + msp->subsym[msp->nsubsym-1] = Symbol_new(&x[1]); + if( islower(x[1]) || islower(msp->subsym[0]->name[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Cannot form a compound containing a non-terminal"); + psp->errorcnt++; + } + }else if( x[0]=='(' && psp->nrhs>0 ){ + psp->state = RHS_ALIAS_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal character on RHS of rule: \"%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case RHS_ALIAS_1: + if( isalpha(x[0]) ){ + psp->alias[psp->nrhs-1] = x; + psp->state = RHS_ALIAS_2; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n", + x,psp->rhs[psp->nrhs-1]->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case RHS_ALIAS_2: + if( x[0]==')' ){ + psp->state = IN_RHS; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case WAITING_FOR_DECL_KEYWORD: + if( isalpha(x[0]) ){ + psp->declkeyword = x; + psp->declargslot = 0; + psp->decllinenoslot = 0; + psp->insertLineMacro = 1; + psp->state = WAITING_FOR_DECL_ARG; + if( strcmp(x,"name")==0 ){ + psp->declargslot = &(psp->gp->name); + psp->insertLineMacro = 0; + }else if( strcmp(x,"include")==0 ){ + psp->declargslot = &(psp->gp->include); + }else if( strcmp(x,"code")==0 ){ + psp->declargslot = &(psp->gp->extracode); + }else if( strcmp(x,"token_destructor")==0 ){ + psp->declargslot = &psp->gp->tokendest; + }else if( strcmp(x,"default_destructor")==0 ){ + psp->declargslot = &psp->gp->vardest; + }else if( strcmp(x,"token_prefix")==0 ){ + psp->declargslot = &psp->gp->tokenprefix; + psp->insertLineMacro = 0; + }else if( strcmp(x,"syntax_error")==0 ){ + psp->declargslot = &(psp->gp->error); + }else if( strcmp(x,"parse_accept")==0 ){ + psp->declargslot = &(psp->gp->accept); + }else if( strcmp(x,"parse_failure")==0 ){ + psp->declargslot = &(psp->gp->failure); + }else if( strcmp(x,"stack_overflow")==0 ){ + psp->declargslot = &(psp->gp->overflow); + }else if( strcmp(x,"extra_argument")==0 ){ + psp->declargslot = &(psp->gp->arg); + psp->insertLineMacro = 0; + }else if( strcmp(x,"token_type")==0 ){ + psp->declargslot = &(psp->gp->tokentype); + psp->insertLineMacro = 0; + }else if( strcmp(x,"default_type")==0 ){ + psp->declargslot = &(psp->gp->vartype); + psp->insertLineMacro = 0; + }else if( strcmp(x,"stack_size")==0 ){ + psp->declargslot = &(psp->gp->stacksize); + psp->insertLineMacro = 0; + }else if( strcmp(x,"start_symbol")==0 ){ + psp->declargslot = &(psp->gp->start); + psp->insertLineMacro = 0; + }else if( strcmp(x,"left")==0 ){ + psp->preccounter++; + psp->declassoc = LEFT; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"right")==0 ){ + psp->preccounter++; + psp->declassoc = RIGHT; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"nonassoc")==0 ){ + psp->preccounter++; + psp->declassoc = NONE; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"destructor")==0 ){ + psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL; + }else if( strcmp(x,"type")==0 ){ + psp->state = WAITING_FOR_DATATYPE_SYMBOL; + }else if( strcmp(x,"fallback")==0 ){ + psp->fallback = 0; + psp->state = WAITING_FOR_FALLBACK_ID; + }else if( strcmp(x,"wildcard")==0 ){ + psp->state = WAITING_FOR_WILDCARD_ID; + }else if( strcmp(x,"token_class")==0 ){ + psp->state = WAITING_FOR_CLASS_ID; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Unknown declaration keyword: \"%%%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal declaration keyword: \"%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case WAITING_FOR_DESTRUCTOR_SYMBOL: + if( !isalpha(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol name missing after %%destructor keyword"); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + struct symbol *sp = Symbol_new(x); + psp->declargslot = &sp->destructor; + psp->decllinenoslot = &sp->destLineno; + psp->insertLineMacro = 1; + psp->state = WAITING_FOR_DECL_ARG; + } + break; + case WAITING_FOR_DATATYPE_SYMBOL: + if( !isalpha(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol name missing after %%type keyword"); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + struct symbol *sp = Symbol_find(x); + if((sp) && (sp->datatype)){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol %%type \"%s\" already defined", x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + if (!sp){ + sp = Symbol_new(x); + } + psp->declargslot = &sp->datatype; + psp->insertLineMacro = 0; + psp->state = WAITING_FOR_DECL_ARG; + } + } + break; + case WAITING_FOR_PRECEDENCE_SYMBOL: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( isupper(x[0]) ){ + struct symbol *sp; + sp = Symbol_new(x); + if( sp->prec>=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol \"%s\" has already be given a precedence.",x); + psp->errorcnt++; + }else{ + sp->prec = psp->preccounter; + sp->assoc = psp->declassoc; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Can't assign a precedence to \"%s\".",x); + psp->errorcnt++; + } + break; + case WAITING_FOR_DECL_ARG: + if( x[0]=='{' || x[0]=='\"' || isalnum(x[0]) ){ + const char *zOld, *zNew; + char *zBuf, *z; + int nOld, n, nLine, nNew, nBack; + int addLineMacro; + char zLine[50]; + zNew = x; + if( zNew[0]=='"' || zNew[0]=='{' ) zNew++; + nNew = lemonStrlen(zNew); + if( *psp->declargslot ){ + zOld = *psp->declargslot; + }else{ + zOld = ""; + } + nOld = lemonStrlen(zOld); + n = nOld + nNew + 20; + addLineMacro = !psp->gp->nolinenosflag && psp->insertLineMacro && + (psp->decllinenoslot==0 || psp->decllinenoslot[0]!=0); + if( addLineMacro ){ + for(z=psp->filename, nBack=0; *z; z++){ + if( *z=='\\' ) nBack++; + } + lemon_sprintf(zLine, "#line %d ", psp->tokenlineno); + nLine = lemonStrlen(zLine); + n += nLine + lemonStrlen(psp->filename) + nBack; + } + *psp->declargslot = (char *) realloc(*psp->declargslot, n); + zBuf = *psp->declargslot + nOld; + if( addLineMacro ){ + if( nOld && zBuf[-1]!='\n' ){ + *(zBuf++) = '\n'; + } + memcpy(zBuf, zLine, nLine); + zBuf += nLine; + *(zBuf++) = '"'; + for(z=psp->filename; *z; z++){ + if( *z=='\\' ){ + *(zBuf++) = '\\'; + } + *(zBuf++) = *z; + } + *(zBuf++) = '"'; + *(zBuf++) = '\n'; + } + if( psp->decllinenoslot && psp->decllinenoslot[0]==0 ){ + psp->decllinenoslot[0] = psp->tokenlineno; + } + memcpy(zBuf, zNew, nNew); + zBuf += nNew; + *zBuf = 0; + psp->state = WAITING_FOR_DECL_OR_RULE; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal argument to %%%s: %s",psp->declkeyword,x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case WAITING_FOR_FALLBACK_ID: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( !isupper(x[0]) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%fallback argument \"%s\" should be a token", x); + psp->errorcnt++; + }else{ + struct symbol *sp = Symbol_new(x); + if( psp->fallback==0 ){ + psp->fallback = sp; + }else if( sp->fallback ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "More than one fallback assigned to token %s", x); + psp->errorcnt++; + }else{ + sp->fallback = psp->fallback; + psp->gp->has_fallback = 1; + } + } + break; + case WAITING_FOR_WILDCARD_ID: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( !isupper(x[0]) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%wildcard argument \"%s\" should be a token", x); + psp->errorcnt++; + }else{ + struct symbol *sp = Symbol_new(x); + if( psp->gp->wildcard==0 ){ + psp->gp->wildcard = sp; + }else{ + ErrorMsg(psp->filename, psp->tokenlineno, + "Extra wildcard to token: %s", x); + psp->errorcnt++; + } + } + break; + case WAITING_FOR_CLASS_ID: + if( !islower(x[0]) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%token_class must be followed by an identifier: ", x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else if( Symbol_find(x) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "Symbol \"%s\" already used", x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + psp->tkclass = Symbol_new(x); + psp->tkclass->type = MULTITERMINAL; + psp->state = WAITING_FOR_CLASS_TOKEN; + } + break; + case WAITING_FOR_CLASS_TOKEN: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( isupper(x[0]) || ((x[0]=='|' || x[0]=='/') && isupper(x[1])) ){ + struct symbol *msp = psp->tkclass; + msp->nsubsym++; + msp->subsym = (struct symbol **) realloc(msp->subsym, + sizeof(struct symbol*)*msp->nsubsym); + if( !isupper(x[0]) ) x++; + msp->subsym[msp->nsubsym-1] = Symbol_new(x); + }else{ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%token_class argument \"%s\" should be a token", x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case RESYNC_AFTER_RULE_ERROR: +/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; +** break; */ + case RESYNC_AFTER_DECL_ERROR: + if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; + if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD; + break; + } +} + +/* Run the preprocessor over the input file text. The global variables +** azDefine[0] through azDefine[nDefine-1] contains the names of all defined +** macros. This routine looks for "%ifdef" and "%ifndef" and "%endif" and +** comments them out. Text in between is also commented out as appropriate. +*/ +static void preprocess_input(char *z){ + int i, j, k, n; + int exclude = 0; + int start = 0; + int lineno = 1; + int start_lineno = 1; + for(i=0; z[i]; i++){ + if( z[i]=='\n' ) lineno++; + if( z[i]!='%' || (i>0 && z[i-1]!='\n') ) continue; + if( strncmp(&z[i],"%endif",6)==0 && isspace(z[i+6]) ){ + if( exclude ){ + exclude--; + if( exclude==0 ){ + for(j=start; jfilename; + ps.errorcnt = 0; + ps.state = INITIALIZE; + + /* Begin by reading the input file */ + fp = fopen(ps.filename,"rb"); + if( fp==0 ){ + ErrorMsg(ps.filename,0,"Can't open this file for reading."); + gp->errorcnt++; + return; + } + fseek(fp,0,2); + filesize = ftell(fp); + rewind(fp); + filebuf = (char *)malloc( filesize+1 ); + if( filesize>100000000 || filebuf==0 ){ + ErrorMsg(ps.filename,0,"Input file too large."); + gp->errorcnt++; + fclose(fp); + return; + } + if( fread(filebuf,1,filesize,fp)!=filesize ){ + ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.", + filesize); + free(filebuf); + gp->errorcnt++; + fclose(fp); + return; + } + fclose(fp); + filebuf[filesize] = 0; + + /* Make an initial pass through the file to handle %ifdef and %ifndef */ + preprocess_input(filebuf); + + /* Now scan the text of the input file */ + lineno = 1; + for(cp=filebuf; (c= *cp)!=0; ){ + if( c=='\n' ) lineno++; /* Keep track of the line number */ + if( isspace(c) ){ cp++; continue; } /* Skip all white space */ + if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */ + cp+=2; + while( (c= *cp)!=0 && c!='\n' ) cp++; + continue; + } + if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */ + cp+=2; + while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){ + if( c=='\n' ) lineno++; + cp++; + } + if( c ) cp++; + continue; + } + ps.tokenstart = cp; /* Mark the beginning of the token */ + ps.tokenlineno = lineno; /* Linenumber on which token begins */ + if( c=='\"' ){ /* String literals */ + cp++; + while( (c= *cp)!=0 && c!='\"' ){ + if( c=='\n' ) lineno++; + cp++; + } + if( c==0 ){ + ErrorMsg(ps.filename,startline, +"String starting on this line is not terminated before the end of the file."); + ps.errorcnt++; + nextcp = cp; + }else{ + nextcp = cp+1; + } + }else if( c=='{' ){ /* A block of C code */ + int level; + cp++; + for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){ + if( c=='\n' ) lineno++; + else if( c=='{' ) level++; + else if( c=='}' ) level--; + else if( c=='/' && cp[1]=='*' ){ /* Skip comments */ + int prevc; + cp = &cp[2]; + prevc = 0; + while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){ + if( c=='\n' ) lineno++; + prevc = c; + cp++; + } + }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */ + cp = &cp[2]; + while( (c= *cp)!=0 && c!='\n' ) cp++; + if( c ) lineno++; + }else if( c=='\'' || c=='\"' ){ /* String a character literals */ + int startchar, prevc; + startchar = c; + prevc = 0; + for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){ + if( c=='\n' ) lineno++; + if( prevc=='\\' ) prevc = 0; + else prevc = c; + } + } + } + if( c==0 ){ + ErrorMsg(ps.filename,ps.tokenlineno, +"C code starting on this line is not terminated before the end of the file."); + ps.errorcnt++; + nextcp = cp; + }else{ + nextcp = cp+1; + } + }else if( isalnum(c) ){ /* Identifiers */ + while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++; + nextcp = cp; + }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */ + cp += 3; + nextcp = cp; + }else if( (c=='/' || c=='|') && isalpha(cp[1]) ){ + cp += 2; + while( (c = *cp)!=0 && (isalnum(c) || c=='_') ) cp++; + nextcp = cp; + }else{ /* All other (one character) operators */ + cp++; + nextcp = cp; + } + c = *cp; + *cp = 0; /* Null terminate the token */ + parseonetoken(&ps); /* Parse the token */ + *cp = c; /* Restore the buffer */ + cp = nextcp; + } + free(filebuf); /* Release the buffer after parsing */ + gp->rule = ps.firstrule; + gp->errorcnt = ps.errorcnt; +} +/*************************** From the file "plink.c" *********************/ +/* +** Routines processing configuration follow-set propagation links +** in the LEMON parser generator. +*/ +static struct plink *plink_freelist = 0; + +/* Allocate a new plink */ +struct plink *Plink_new(){ + struct plink *newlink; + + if( plink_freelist==0 ){ + int i; + int amt = 100; + plink_freelist = (struct plink *)calloc( amt, sizeof(struct plink) ); + if( plink_freelist==0 ){ + fprintf(stderr, + "Unable to allocate memory for a new follow-set propagation link.\n"); + exit(1); + } + for(i=0; inext; + return newlink; +} + +/* Add a plink to a plink list */ +void Plink_add(struct plink **plpp, struct config *cfp) +{ + struct plink *newlink; + newlink = Plink_new(); + newlink->next = *plpp; + *plpp = newlink; + newlink->cfp = cfp; +} + +/* Transfer every plink on the list "from" to the list "to" */ +void Plink_copy(struct plink **to, struct plink *from) +{ + struct plink *nextpl; + while( from ){ + nextpl = from->next; + from->next = *to; + *to = from; + from = nextpl; + } +} + +/* Delete every plink on the list */ +void Plink_delete(struct plink *plp) +{ + struct plink *nextpl; + + while( plp ){ + nextpl = plp->next; + plp->next = plink_freelist; + plink_freelist = plp; + plp = nextpl; + } +} +/*********************** From the file "report.c" **************************/ +/* +** Procedures for generating reports and tables in the LEMON parser generator. +*/ + +/* Generate a filename with the given suffix. Space to hold the +** name comes from malloc() and must be freed by the calling +** function. +*/ +PRIVATE char *file_makename(struct lemon *lemp, const char *suffix) +{ + char *name; + char *cp; + + name = (char*)malloc( lemonStrlen(lemp->filename) + lemonStrlen(suffix) + 5 ); + if( name==0 ){ + fprintf(stderr,"Can't allocate space for a filename.\n"); + exit(1); + } + lemon_strcpy(name,lemp->filename); + cp = strrchr(name,'.'); + if( cp ) *cp = 0; + lemon_strcat(name,suffix); + return name; +} + +/* Open a file with a name based on the name of the input file, +** but with a different (specified) suffix, and return a pointer +** to the stream */ +PRIVATE FILE *file_open( + struct lemon *lemp, + const char *suffix, + const char *mode +){ + FILE *fp; + + if( lemp->outname ) free(lemp->outname); + lemp->outname = file_makename(lemp, suffix); + fp = fopen(lemp->outname,mode); + if( fp==0 && *mode=='w' ){ + fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname); + lemp->errorcnt++; + return 0; + } + return fp; +} + +/* Duplicate the input file without comments and without actions +** on rules */ +void Reprint(struct lemon *lemp) +{ + struct rule *rp; + struct symbol *sp; + int i, j, maxlen, len, ncolumns, skip; + printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename); + maxlen = 10; + for(i=0; insymbol; i++){ + sp = lemp->symbols[i]; + len = lemonStrlen(sp->name); + if( len>maxlen ) maxlen = len; + } + ncolumns = 76/(maxlen+5); + if( ncolumns<1 ) ncolumns = 1; + skip = (lemp->nsymbol + ncolumns - 1)/ncolumns; + for(i=0; insymbol; j+=skip){ + sp = lemp->symbols[j]; + assert( sp->index==j ); + printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name); + } + printf("\n"); + } + for(rp=lemp->rule; rp; rp=rp->next){ + printf("%s",rp->lhs->name); + /* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */ + printf(" ::="); + for(i=0; inrhs; i++){ + sp = rp->rhs[i]; + if( sp->type==MULTITERMINAL ){ + printf(" %s", sp->subsym[0]->name); + for(j=1; jnsubsym; j++){ + printf("|%s", sp->subsym[j]->name); + } + }else{ + printf(" %s", sp->name); + } + /* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */ + } + printf("."); + if( rp->precsym ) printf(" [%s]",rp->precsym->name); + /* if( rp->code ) printf("\n %s",rp->code); */ + printf("\n"); + } +} + +void ConfigPrint(FILE *fp, struct config *cfp) +{ + struct rule *rp; + struct symbol *sp; + int i, j; + rp = cfp->rp; + fprintf(fp,"%s ::=",rp->lhs->name); + for(i=0; i<=rp->nrhs; i++){ + if( i==cfp->dot ) fprintf(fp," *"); + if( i==rp->nrhs ) break; + sp = rp->rhs[i]; + if( sp->type==MULTITERMINAL ){ + fprintf(fp," %s", sp->subsym[0]->name); + for(j=1; jnsubsym; j++){ + fprintf(fp,"|%s",sp->subsym[j]->name); + } + }else{ + fprintf(fp," %s", sp->name); + } + } +} + +/* #define TEST */ +#if 0 +/* Print a set */ +PRIVATE void SetPrint(out,set,lemp) +FILE *out; +char *set; +struct lemon *lemp; +{ + int i; + char *spacer; + spacer = ""; + fprintf(out,"%12s[",""); + for(i=0; interminal; i++){ + if( SetFind(set,i) ){ + fprintf(out,"%s%s",spacer,lemp->symbols[i]->name); + spacer = " "; + } + } + fprintf(out,"]\n"); +} + +/* Print a plink chain */ +PRIVATE void PlinkPrint(out,plp,tag) +FILE *out; +struct plink *plp; +char *tag; +{ + while( plp ){ + fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->statenum); + ConfigPrint(out,plp->cfp); + fprintf(out,"\n"); + plp = plp->next; + } +} +#endif + +/* Print an action to the given file descriptor. Return FALSE if +** nothing was actually printed. +*/ +int PrintAction(struct action *ap, FILE *fp, int indent){ + int result = 1; + switch( ap->type ){ + case SHIFT: + fprintf(fp,"%*s shift %d",indent,ap->sp->name,ap->x.stp->statenum); + break; + case REDUCE: + fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index); + break; + case ACCEPT: + fprintf(fp,"%*s accept",indent,ap->sp->name); + break; + case ERROR: + fprintf(fp,"%*s error",indent,ap->sp->name); + break; + case SRCONFLICT: + case RRCONFLICT: + fprintf(fp,"%*s reduce %-3d ** Parsing conflict **", + indent,ap->sp->name,ap->x.rp->index); + break; + case SSCONFLICT: + fprintf(fp,"%*s shift %-3d ** Parsing conflict **", + indent,ap->sp->name,ap->x.stp->statenum); + break; + case SH_RESOLVED: + if( showPrecedenceConflict ){ + fprintf(fp,"%*s shift %-3d -- dropped by precedence", + indent,ap->sp->name,ap->x.stp->statenum); + }else{ + result = 0; + } + break; + case RD_RESOLVED: + if( showPrecedenceConflict ){ + fprintf(fp,"%*s reduce %-3d -- dropped by precedence", + indent,ap->sp->name,ap->x.rp->index); + }else{ + result = 0; + } + break; + case NOT_USED: + result = 0; + break; + } + return result; +} + +/* Generate the "y.output" log file */ +void ReportOutput(struct lemon *lemp) +{ + int i; + struct state *stp; + struct config *cfp; + struct action *ap; + FILE *fp; + + fp = file_open(lemp,".out","wb"); + if( fp==0 ) return; + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + fprintf(fp,"State %d:\n",stp->statenum); + if( lemp->basisflag ) cfp=stp->bp; + else cfp=stp->cfp; + while( cfp ){ + char buf[20]; + if( cfp->dot==cfp->rp->nrhs ){ + lemon_sprintf(buf,"(%d)",cfp->rp->index); + fprintf(fp," %5s ",buf); + }else{ + fprintf(fp," "); + } + ConfigPrint(fp,cfp); + fprintf(fp,"\n"); +#if 0 + SetPrint(fp,cfp->fws,lemp); + PlinkPrint(fp,cfp->fplp,"To "); + PlinkPrint(fp,cfp->bplp,"From"); +#endif + if( lemp->basisflag ) cfp=cfp->bp; + else cfp=cfp->next; + } + fprintf(fp,"\n"); + for(ap=stp->ap; ap; ap=ap->next){ + if( PrintAction(ap,fp,30) ) fprintf(fp,"\n"); + } + fprintf(fp,"\n"); + } + fprintf(fp, "----------------------------------------------------\n"); + fprintf(fp, "Symbols:\n"); + for(i=0; insymbol; i++){ + int j; + struct symbol *sp; + + sp = lemp->symbols[i]; + fprintf(fp, " %3d: %s", i, sp->name); + if( sp->type==NONTERMINAL ){ + fprintf(fp, ":"); + if( sp->lambda ){ + fprintf(fp, " "); + } + for(j=0; jnterminal; j++){ + if( sp->firstset && SetFind(sp->firstset, j) ){ + fprintf(fp, " %s", lemp->symbols[j]->name); + } + } + } + fprintf(fp, "\n"); + } + fclose(fp); + return; +} + +/* Search for the file "name" which is in the same directory as +** the exacutable */ +PRIVATE char *pathsearch(char *argv0, char *name, int modemask) +{ + const char *pathlist; + char *pathbufptr; + char *pathbuf; + char *path,*cp; + char c; + +#ifdef __WIN32__ + cp = strrchr(argv0,'\\'); +#else + cp = strrchr(argv0,'/'); +#endif + if( cp ){ + c = *cp; + *cp = 0; + path = (char *)malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 ); + if( path ) lemon_sprintf(path,"%s/%s",argv0,name); + *cp = c; + }else{ + pathlist = getenv("PATH"); + if( pathlist==0 ) pathlist = ".:/bin:/usr/bin"; + pathbuf = (char *) malloc( lemonStrlen(pathlist) + 1 ); + path = (char *)malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 ); + if( (pathbuf != 0) && (path!=0) ){ + pathbufptr = pathbuf; + lemon_strcpy(pathbuf, pathlist); + while( *pathbuf ){ + cp = strchr(pathbuf,':'); + if( cp==0 ) cp = &pathbuf[lemonStrlen(pathbuf)]; + c = *cp; + *cp = 0; + lemon_sprintf(path,"%s/%s",pathbuf,name); + *cp = c; + if( c==0 ) pathbuf[0] = 0; + else pathbuf = &cp[1]; + if( access(path,modemask)==0 ) break; + } + free(pathbufptr); + } + } + return path; +} + +/* Given an action, compute the integer value for that action +** which is to be put in the action table of the generated machine. +** Return negative if no action should be generated. +*/ +PRIVATE int compute_action(struct lemon *lemp, struct action *ap) +{ + int act; + switch( ap->type ){ + case SHIFT: act = ap->x.stp->statenum; break; + case REDUCE: act = ap->x.rp->index + lemp->nstate; break; + case ERROR: act = lemp->nstate + lemp->nrule; break; + case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break; + default: act = -1; break; + } + return act; +} + +#define LINESIZE 1000 +/* The next cluster of routines are for reading the template file +** and writing the results to the generated parser */ +/* The first function transfers data from "in" to "out" until +** a line is seen which begins with "%%". The line number is +** tracked. +** +** if name!=0, then any word that begin with "Parse" is changed to +** begin with *name instead. +*/ +PRIVATE void tplt_xfer(char *name, FILE *in, FILE *out, int *lineno) +{ + int i, iStart; + char line[LINESIZE]; + while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){ + (*lineno)++; + iStart = 0; + if( name ){ + for(i=0; line[i]; i++){ + if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0 + && (i==0 || !isalpha(line[i-1])) + ){ + if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]); + fprintf(out,"%s",name); + i += 4; + iStart = i+1; + } + } + } + fprintf(out,"%s",&line[iStart]); + } +} + +/* The next function finds the template file and opens it, returning +** a pointer to the opened file. */ +PRIVATE FILE *tplt_open(struct lemon *lemp) +{ + static char templatename[] = "lempar.c"; + char buf[1000]; + FILE *in; + char *tpltname; + char *cp; + + /* first, see if user specified a template filename on the command line. */ + if (user_templatename != 0) { + if( access(user_templatename,004)==-1 ){ + fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", + user_templatename); + lemp->errorcnt++; + return 0; + } + in = fopen(user_templatename,"rb"); + if( in==0 ){ + fprintf(stderr,"Can't open the template file \"%s\".\n",user_templatename); + lemp->errorcnt++; + return 0; + } + return in; + } + + cp = strrchr(lemp->filename,'.'); + if( cp ){ + lemon_sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename); + }else{ + lemon_sprintf(buf,"%s.lt",lemp->filename); + } + if( access(buf,004)==0 ){ + tpltname = buf; + }else if( access(templatename,004)==0 ){ + tpltname = templatename; + }else{ + tpltname = pathsearch(lemp->argv0,templatename,0); + } + if( tpltname==0 ){ + fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", + templatename); + lemp->errorcnt++; + return 0; + } + in = fopen(tpltname,"rb"); + if( in==0 ){ + fprintf(stderr,"Can't open the template file \"%s\".\n",templatename); + lemp->errorcnt++; + return 0; + } + return in; +} + +/* Print a #line directive line to the output file. */ +PRIVATE void tplt_linedir(FILE *out, int lineno, char *filename) +{ + fprintf(out,"#line %d \"",lineno); + while( *filename ){ + if( *filename == '\\' ) putc('\\',out); + putc(*filename,out); + filename++; + } + fprintf(out,"\"\n"); +} + +/* Print a string to the file and keep the linenumber up to date */ +PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno) +{ + if( str==0 ) return; + while( *str ){ + putc(*str,out); + if( *str=='\n' ) (*lineno)++; + str++; + } + if( str[-1]!='\n' ){ + putc('\n',out); + (*lineno)++; + } + if (!lemp->nolinenosflag) { + (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); + } + return; +} + +/* +** The following routine emits code for the destructor for the +** symbol sp +*/ +void emit_destructor_code( + FILE *out, + struct symbol *sp, + struct lemon *lemp, + int *lineno +){ + char *cp = 0; + + if( sp->type==TERMINAL ){ + cp = lemp->tokendest; + if( cp==0 ) return; + fprintf(out,"{\n"); (*lineno)++; + }else if( sp->destructor ){ + cp = sp->destructor; + fprintf(out,"{\n"); (*lineno)++; + if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,sp->destLineno,lemp->filename); } + }else if( lemp->vardest ){ + cp = lemp->vardest; + if( cp==0 ) return; + fprintf(out,"{\n"); (*lineno)++; + }else{ + assert( 0 ); /* Cannot happen */ + } + for(; *cp; cp++){ + if( *cp=='$' && cp[1]=='$' ){ + fprintf(out,"(yypminor->yy%d)",sp->dtnum); + cp++; + continue; + } + if( *cp=='\n' ) (*lineno)++; + fputc(*cp,out); + } + fprintf(out,"\n"); (*lineno)++; + if (!lemp->nolinenosflag) { + (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); + } + fprintf(out,"}\n"); (*lineno)++; + return; +} + +/* +** Return TRUE (non-zero) if the given symbol has a destructor. +*/ +int has_destructor(struct symbol *sp, struct lemon *lemp) +{ + int ret; + if( sp->type==TERMINAL ){ + ret = lemp->tokendest!=0; + }else{ + ret = lemp->vardest!=0 || sp->destructor!=0; + } + return ret; +} + +/* +** Append text to a dynamically allocated string. If zText is 0 then +** reset the string to be empty again. Always return the complete text +** of the string (which is overwritten with each call). +** +** n bytes of zText are stored. If n==0 then all of zText up to the first +** \000 terminator is stored. zText can contain up to two instances of +** %d. The values of p1 and p2 are written into the first and second +** %d. +** +** If n==-1, then the previous character is overwritten. +*/ +PRIVATE char *append_str(const char *zText, int n, int p1, int p2){ + static char empty[1] = { 0 }; + static char *z = 0; + static int alloced = 0; + static int used = 0; + int c; + char zInt[40]; + if( zText==0 ){ + used = 0; + return z; + } + if( n<=0 ){ + if( n<0 ){ + used += n; + assert( used>=0 ); + } + n = lemonStrlen(zText); + } + if( (int) (n+sizeof(zInt)*2+used) >= alloced ){ + alloced = n + sizeof(zInt)*2 + used + 200; + z = (char *) realloc(z, alloced); + } + if( z==0 ) return empty; + while( n-- > 0 ){ + c = *(zText++); + if( c=='%' && n>0 && zText[0]=='d' ){ + lemon_sprintf(zInt, "%d", p1); + p1 = p2; + lemon_strcpy(&z[used], zInt); + used += lemonStrlen(&z[used]); + zText++; + n--; + }else{ + z[used++] = c; + } + } + z[used] = 0; + return z; +} + +/* +** zCode is a string that is the action associated with a rule. Expand +** the symbols in this string so that the refer to elements of the parser +** stack. +*/ +PRIVATE void translate_code(struct lemon *lemp, struct rule *rp){ + char *cp, *xp; + int i; + char lhsused = 0; /* True if the LHS element has been used */ + char used[MAXRHS]; /* True for each RHS element which is used */ + + for(i=0; inrhs; i++) used[i] = 0; + lhsused = 0; + + if( rp->code==0 ){ + static char newlinestr[2] = { '\n', '\0' }; + rp->code = newlinestr; + rp->line = rp->ruleline; + } + + append_str(0,0,0,0); + + /* This const cast is wrong but harmless, if we're careful. */ + for(cp=(char *)rp->code; *cp; cp++){ + if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){ + char saved; + for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++); + saved = *xp; + *xp = 0; + if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){ + append_str("yygotominor.yy%d",0,rp->lhs->dtnum,0); + cp = xp; + lhsused = 1; + }else{ + for(i=0; inrhs; i++){ + if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){ + if( cp!=rp->code && cp[-1]=='@' ){ + /* If the argument is of the form @X then substituted + ** the token number of X, not the value of X */ + append_str("yymsp[%d].major",-1,i-rp->nrhs+1,0); + }else{ + struct symbol *sp = rp->rhs[i]; + int dtnum; + if( sp->type==MULTITERMINAL ){ + dtnum = sp->subsym[0]->dtnum; + }else{ + dtnum = sp->dtnum; + } + append_str("yymsp[%d].minor.yy%d",0,i-rp->nrhs+1, dtnum); + } + cp = xp; + used[i] = 1; + break; + } + } + } + *xp = saved; + } + append_str(cp, 1, 0, 0); + } /* End loop */ + + /* Check to make sure the LHS has been used */ + if( rp->lhsalias && !lhsused ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label \"%s\" for \"%s(%s)\" is never used.", + rp->lhsalias,rp->lhs->name,rp->lhsalias); + lemp->errorcnt++; + } + + /* Generate destructor code for RHS symbols which are not used in the + ** reduce code */ + for(i=0; inrhs; i++){ + if( rp->rhsalias[i] && !used[i] ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label %s for \"%s(%s)\" is never used.", + rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]); + lemp->errorcnt++; + }else if( rp->rhsalias[i]==0 ){ + if( has_destructor(rp->rhs[i],lemp) ){ + append_str(" yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0, + rp->rhs[i]->index,i-rp->nrhs+1); + }else{ + /* No destructor defined for this term */ + } + } + } + if( rp->code ){ + cp = append_str(0,0,0,0); + rp->code = Strsafe(cp?cp:""); + } +} + +/* +** Generate code which executes when the rule "rp" is reduced. Write +** the code to "out". Make sure lineno stays up-to-date. +*/ +PRIVATE void emit_code( + FILE *out, + struct rule *rp, + struct lemon *lemp, + int *lineno +){ + const char *cp; + + /* Generate code to do the reduce action */ + if( rp->code ){ + if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,rp->line,lemp->filename); } + fprintf(out,"{%s",rp->code); + for(cp=rp->code; *cp; cp++){ + if( *cp=='\n' ) (*lineno)++; + } /* End loop */ + fprintf(out,"}\n"); (*lineno)++; + if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); } + } /* End if( rp->code ) */ + + return; +} + +/* +** Print the definition of the union used for the parser's data stack. +** This union contains fields for every possible data type for tokens +** and nonterminals. In the process of computing and printing this +** union, also set the ".dtnum" field of every terminal and nonterminal +** symbol. +*/ +void print_stack_union( + FILE *out, /* The output stream */ + struct lemon *lemp, /* The main info structure for this parser */ + int *plineno, /* Pointer to the line number */ + int mhflag /* True if generating makeheaders output */ +){ + int lineno = *plineno; /* The line number of the output */ + char **types; /* A hash table of datatypes */ + int arraysize; /* Size of the "types" array */ + int maxdtlength; /* Maximum length of any ".datatype" field. */ + char *stddt; /* Standardized name for a datatype */ + int i,j; /* Loop counters */ + unsigned hash; /* For hashing the name of a type */ + const char *name; /* Name of the parser */ + + /* Allocate and initialize types[] and allocate stddt[] */ + arraysize = lemp->nsymbol * 2; + types = (char**)calloc( arraysize, sizeof(char*) ); + if( types==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + for(i=0; ivartype ){ + maxdtlength = lemonStrlen(lemp->vartype); + } + for(i=0; insymbol; i++){ + int len; + struct symbol *sp = lemp->symbols[i]; + if( sp->datatype==0 ) continue; + len = lemonStrlen(sp->datatype); + if( len>maxdtlength ) maxdtlength = len; + } + stddt = (char*)malloc( maxdtlength*2 + 1 ); + if( stddt==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + + /* Build a hash table of datatypes. The ".dtnum" field of each symbol + ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is + ** used for terminal symbols. If there is no %default_type defined then + ** 0 is also used as the .dtnum value for nonterminals which do not specify + ** a datatype using the %type directive. + */ + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + char *cp; + if( sp==lemp->errsym ){ + sp->dtnum = arraysize+1; + continue; + } + if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){ + sp->dtnum = 0; + continue; + } + cp = sp->datatype; + if( cp==0 ) cp = lemp->vartype; + j = 0; + while( isspace(*cp) ) cp++; + while( *cp ) stddt[j++] = *cp++; + while( j>0 && isspace(stddt[j-1]) ) j--; + stddt[j] = 0; + if( lemp->tokentype && strcmp(stddt, lemp->tokentype)==0 ){ + sp->dtnum = 0; + continue; + } + hash = 0; + for(j=0; stddt[j]; j++){ + hash = hash*53 + stddt[j]; + } + hash = (hash & 0x7fffffff)%arraysize; + while( types[hash] ){ + if( strcmp(types[hash],stddt)==0 ){ + sp->dtnum = hash + 1; + break; + } + hash++; + if( hash>=(unsigned)arraysize ) hash = 0; + } + if( types[hash]==0 ){ + sp->dtnum = hash + 1; + types[hash] = (char*)malloc( lemonStrlen(stddt)+1 ); + if( types[hash]==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + lemon_strcpy(types[hash],stddt); + } + } + + /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */ + name = lemp->name ? lemp->name : "Parse"; + lineno = *plineno; + if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; } + fprintf(out,"#define %sTOKENTYPE %s\n",name, + lemp->tokentype?lemp->tokentype:"void*"); lineno++; + if( mhflag ){ fprintf(out,"#endif\n"); lineno++; } + fprintf(out,"typedef union {\n"); lineno++; + fprintf(out," int yyinit;\n"); lineno++; + fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++; + for(i=0; ierrsym->useCnt ){ + fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++; + } + free(stddt); + free(types); + fprintf(out,"} YYMINORTYPE;\n"); lineno++; + *plineno = lineno; +} + +/* +** Return the name of a C datatype able to represent values between +** lwr and upr, inclusive. +*/ +static const char *minimum_size_type(int lwr, int upr){ + if( lwr>=0 ){ + if( upr<=255 ){ + return "unsigned char"; + }else if( upr<65535 ){ + return "unsigned short int"; + }else{ + return "unsigned int"; + } + }else if( lwr>=-127 && upr<=127 ){ + return "signed char"; + }else if( lwr>=-32767 && upr<32767 ){ + return "short"; + }else{ + return "int"; + } +} + +/* +** Each state contains a set of token transaction and a set of +** nonterminal transactions. Each of these sets makes an instance +** of the following structure. An array of these structures is used +** to order the creation of entries in the yy_action[] table. +*/ +struct axset { + struct state *stp; /* A pointer to a state */ + int isTkn; /* True to use tokens. False for non-terminals */ + int nAction; /* Number of actions */ + int iOrder; /* Original order of action sets */ +}; + +/* +** Compare to axset structures for sorting purposes +*/ +static int axset_compare(const void *a, const void *b){ + struct axset *p1 = (struct axset*)a; + struct axset *p2 = (struct axset*)b; + int c; + c = p2->nAction - p1->nAction; + if( c==0 ){ + c = p2->iOrder - p1->iOrder; + } + assert( c!=0 || p1==p2 ); + return c; +} + +/* +** Write text on "out" that describes the rule "rp". +*/ +static void writeRuleText(FILE *out, struct rule *rp){ + int j; + fprintf(out,"%s ::=", rp->lhs->name); + for(j=0; jnrhs; j++){ + struct symbol *sp = rp->rhs[j]; + if( sp->type!=MULTITERMINAL ){ + fprintf(out," %s", sp->name); + }else{ + int k; + fprintf(out," %s", sp->subsym[0]->name); + for(k=1; knsubsym; k++){ + fprintf(out,"|%s",sp->subsym[k]->name); + } + } + } +} + + +/* Generate C source code for the parser */ +void ReportTable( + struct lemon *lemp, + int mhflag /* Output in makeheaders format if true */ +){ + FILE *out, *in; + char line[LINESIZE]; + int lineno; + struct state *stp; + struct action *ap; + struct rule *rp; + struct acttab *pActtab; + int i, j, n; + const char *name; + int mnTknOfst, mxTknOfst; + int mnNtOfst, mxNtOfst; + struct axset *ax; + + in = tplt_open(lemp); + if( in==0 ) return; + out = file_open(lemp,".c","wb"); + if( out==0 ){ + fclose(in); + return; + } + lineno = 1; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the include code, if any */ + tplt_print(out,lemp,lemp->include,&lineno); + if( mhflag ){ + char *name = file_makename(lemp, ".h"); + fprintf(out,"#include \"%s\"\n", name); lineno++; + free(name); + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate #defines for all tokens */ + if( mhflag ){ + const char *prefix; + fprintf(out,"#if INTERFACE\n"); lineno++; + if( lemp->tokenprefix ) prefix = lemp->tokenprefix; + else prefix = ""; + for(i=1; interminal; i++){ + fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); + lineno++; + } + fprintf(out,"#endif\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the defines */ + fprintf(out,"#define YYCODETYPE %s\n", + minimum_size_type(0, lemp->nsymbol+1)); lineno++; + fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++; + fprintf(out,"#define YYACTIONTYPE %s\n", + minimum_size_type(0, lemp->nstate+lemp->nrule+5)); lineno++; + if( lemp->wildcard ){ + fprintf(out,"#define YYWILDCARD %d\n", + lemp->wildcard->index); lineno++; + } + print_stack_union(out,lemp,&lineno,mhflag); + fprintf(out, "#ifndef YYSTACKDEPTH\n"); lineno++; + if( lemp->stacksize ){ + fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++; + }else{ + fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++; + } + fprintf(out, "#endif\n"); lineno++; + if( mhflag ){ + fprintf(out,"#if INTERFACE\n"); lineno++; + } + name = lemp->name ? lemp->name : "Parse"; + if( lemp->arg && lemp->arg[0] ){ + int i; + i = lemonStrlen(lemp->arg); + while( i>=1 && isspace(lemp->arg[i-1]) ) i--; + while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--; + fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++; + fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++; + fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n", + name,lemp->arg,&lemp->arg[i]); lineno++; + fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n", + name,&lemp->arg[i],&lemp->arg[i]); lineno++; + }else{ + fprintf(out,"#define %sARG_SDECL\n",name); lineno++; + fprintf(out,"#define %sARG_PDECL\n",name); lineno++; + fprintf(out,"#define %sARG_FETCH\n",name); lineno++; + fprintf(out,"#define %sARG_STORE\n",name); lineno++; + } + if( mhflag ){ + fprintf(out,"#endif\n"); lineno++; + } + fprintf(out,"#define YYNSTATE %d\n",lemp->nstate); lineno++; + fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++; + if( lemp->errsym->useCnt ){ + fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; + fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; + } + if( lemp->has_fallback ){ + fprintf(out,"#define YYFALLBACK 1\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the action table and its associates: + ** + ** yy_action[] A single table containing all actions. + ** yy_lookahead[] A table containing the lookahead for each entry in + ** yy_action. Used to detect hash collisions. + ** yy_shift_ofst[] For each state, the offset into yy_action for + ** shifting terminals. + ** yy_reduce_ofst[] For each state, the offset into yy_action for + ** shifting non-terminals after a reduce. + ** yy_default[] Default action for each state. + */ + + /* Compute the actions on all states and count them up */ + ax = (struct axset *) calloc(lemp->nstate*2, sizeof(ax[0])); + if( ax==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + ax[i*2].stp = stp; + ax[i*2].isTkn = 1; + ax[i*2].nAction = stp->nTknAct; + ax[i*2+1].stp = stp; + ax[i*2+1].isTkn = 0; + ax[i*2+1].nAction = stp->nNtAct; + } + mxTknOfst = mnTknOfst = 0; + mxNtOfst = mnNtOfst = 0; + + /* Compute the action table. In order to try to keep the size of the + ** action table to a minimum, the heuristic of placing the largest action + ** sets first is used. + */ + for(i=0; instate*2; i++) ax[i].iOrder = i; + qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare); + pActtab = acttab_alloc(); + for(i=0; instate*2 && ax[i].nAction>0; i++){ + stp = ax[i].stp; + if( ax[i].isTkn ){ + for(ap=stp->ap; ap; ap=ap->next){ + int action; + if( ap->sp->index>=lemp->nterminal ) continue; + action = compute_action(lemp, ap); + if( action<0 ) continue; + acttab_action(pActtab, ap->sp->index, action); + } + stp->iTknOfst = acttab_insert(pActtab); + if( stp->iTknOfstiTknOfst; + if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst; + }else{ + for(ap=stp->ap; ap; ap=ap->next){ + int action; + if( ap->sp->indexnterminal ) continue; + if( ap->sp->index==lemp->nsymbol ) continue; + action = compute_action(lemp, ap); + if( action<0 ) continue; + acttab_action(pActtab, ap->sp->index, action); + } + stp->iNtOfst = acttab_insert(pActtab); + if( stp->iNtOfstiNtOfst; + if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst; + } + } + free(ax); + + /* Output the yy_action table */ + n = acttab_size(pActtab); + fprintf(out,"#define YY_ACTTAB_COUNT (%d)\n", n); lineno++; + fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++; + for(i=j=0; instate + lemp->nrule + 2; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", action); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_lookahead table */ + fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++; + for(i=j=0; insymbol; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", la); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_shift_ofst[] table */ + fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++; + n = lemp->nstate; + while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--; + fprintf(out, "#define YY_SHIFT_COUNT (%d)\n", n-1); lineno++; + fprintf(out, "#define YY_SHIFT_MIN (%d)\n", mnTknOfst); lineno++; + fprintf(out, "#define YY_SHIFT_MAX (%d)\n", mxTknOfst); lineno++; + fprintf(out, "static const %s yy_shift_ofst[] = {\n", + minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++; + for(i=j=0; isorted[i]; + ofst = stp->iTknOfst; + if( ofst==NO_OFFSET ) ofst = mnTknOfst - 1; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", ofst); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_reduce_ofst[] table */ + fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++; + n = lemp->nstate; + while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--; + fprintf(out, "#define YY_REDUCE_COUNT (%d)\n", n-1); lineno++; + fprintf(out, "#define YY_REDUCE_MIN (%d)\n", mnNtOfst); lineno++; + fprintf(out, "#define YY_REDUCE_MAX (%d)\n", mxNtOfst); lineno++; + fprintf(out, "static const %s yy_reduce_ofst[] = {\n", + minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++; + for(i=j=0; isorted[i]; + ofst = stp->iNtOfst; + if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", ofst); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the default action table */ + fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++; + n = lemp->nstate; + for(i=j=0; isorted[i]; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", stp->iDflt); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the table of fallback tokens. + */ + if( lemp->has_fallback ){ + int mx = lemp->nterminal - 1; + while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; } + for(i=0; i<=mx; i++){ + struct symbol *p = lemp->symbols[i]; + if( p->fallback==0 ){ + fprintf(out, " 0, /* %10s => nothing */\n", p->name); + }else{ + fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index, + p->name, p->fallback->name); + } + lineno++; + } + } + tplt_xfer(lemp->name, in, out, &lineno); + + /* Generate a table containing the symbolic name of every symbol + */ + for(i=0; insymbol; i++){ + lemon_sprintf(line,"\"%s\",",lemp->symbols[i]->name); + fprintf(out," %-15s",line); + if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; } + } + if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate a table containing a text string that describes every + ** rule in the rule set of the grammar. This information is used + ** when tracing REDUCE actions. + */ + for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){ + assert( rp->index==i ); + fprintf(out," /* %3d */ \"", i); + writeRuleText(out, rp); + fprintf(out,"\",\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes every time a symbol is popped from + ** the stack while processing errors or while destroying the parser. + ** (In other words, generate the %destructor actions) + */ + if( lemp->tokendest ){ + int once = 1; + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type!=TERMINAL ) continue; + if( once ){ + fprintf(out, " /* TERMINAL Destructor */\n"); lineno++; + once = 0; + } + fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; + } + for(i=0; insymbol && lemp->symbols[i]->type!=TERMINAL; i++); + if( insymbol ){ + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + } + if( lemp->vardest ){ + struct symbol *dflt_sp = 0; + int once = 1; + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type==TERMINAL || + sp->index<=0 || sp->destructor!=0 ) continue; + if( once ){ + fprintf(out, " /* Default NON-TERMINAL Destructor */\n"); lineno++; + once = 0; + } + fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; + dflt_sp = sp; + } + if( dflt_sp!=0 ){ + emit_destructor_code(out,dflt_sp,lemp,&lineno); + } + fprintf(out," break;\n"); lineno++; + } + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue; + fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; + + /* Combine duplicate destructors into a single case */ + for(j=i+1; jnsymbol; j++){ + struct symbol *sp2 = lemp->symbols[j]; + if( sp2 && sp2->type!=TERMINAL && sp2->destructor + && sp2->dtnum==sp->dtnum + && strcmp(sp->destructor,sp2->destructor)==0 ){ + fprintf(out," case %d: /* %s */\n", + sp2->index, sp2->name); lineno++; + sp2->destructor = 0; + } + } + + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes whenever the parser stack overflows */ + tplt_print(out,lemp,lemp->overflow,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the table of rule information + ** + ** Note: This code depends on the fact that rules are number + ** sequentually beginning with 0. + */ + for(rp=lemp->rule; rp; rp=rp->next){ + fprintf(out," { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which execution during each REDUCE action */ + for(rp=lemp->rule; rp; rp=rp->next){ + translate_code(lemp, rp); + } + /* First output rules other than the default: rule */ + for(rp=lemp->rule; rp; rp=rp->next){ + struct rule *rp2; /* Other rules with the same action */ + if( rp->code==0 ) continue; + if( rp->code[0]=='\n' && rp->code[1]==0 ) continue; /* Will be default: */ + fprintf(out," case %d: /* ", rp->index); + writeRuleText(out, rp); + fprintf(out, " */\n"); lineno++; + for(rp2=rp->next; rp2; rp2=rp2->next){ + if( rp2->code==rp->code ){ + fprintf(out," case %d: /* ", rp2->index); + writeRuleText(out, rp2); + fprintf(out," */ yytestcase(yyruleno==%d);\n", rp2->index); lineno++; + rp2->code = 0; + } + } + emit_code(out,rp,lemp,&lineno); + fprintf(out," break;\n"); lineno++; + rp->code = 0; + } + /* Finally, output the default: rule. We choose as the default: all + ** empty actions. */ + fprintf(out," default:\n"); lineno++; + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->code==0 ) continue; + assert( rp->code[0]=='\n' && rp->code[1]==0 ); + fprintf(out," /* (%d) ", rp->index); + writeRuleText(out, rp); + fprintf(out, " */ yytestcase(yyruleno==%d);\n", rp->index); lineno++; + } + fprintf(out," break;\n"); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes if a parse fails */ + tplt_print(out,lemp,lemp->failure,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes when a syntax error occurs */ + tplt_print(out,lemp,lemp->error,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes when the parser accepts its input */ + tplt_print(out,lemp,lemp->accept,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Append any addition code the user desires */ + tplt_print(out,lemp,lemp->extracode,&lineno); + + fclose(in); + fclose(out); + return; +} + +/* Generate a header file for the parser */ +void ReportHeader(struct lemon *lemp) +{ + FILE *out, *in; + const char *prefix; + char line[LINESIZE]; + char pattern[LINESIZE]; + int i; + + if( lemp->tokenprefix ) prefix = lemp->tokenprefix; + else prefix = ""; + in = file_open(lemp,".h","rb"); + if( in ){ + int nextChar; + for(i=1; interminal && fgets(line,LINESIZE,in); i++){ + lemon_sprintf(pattern,"#define %s%-30s %3d\n", + prefix,lemp->symbols[i]->name,i); + if( strcmp(line,pattern) ) break; + } + nextChar = fgetc(in); + fclose(in); + if( i==lemp->nterminal && nextChar==EOF ){ + /* No change in the file. Don't rewrite it. */ + return; + } + } + out = file_open(lemp,".h","wb"); + if( out ){ + for(i=1; interminal; i++){ + fprintf(out,"#define %s%-30s %3d\n",prefix,lemp->symbols[i]->name,i); + } + fclose(out); + } + return; +} + +/* Reduce the size of the action tables, if possible, by making use +** of defaults. +** +** In this version, we take the most frequent REDUCE action and make +** it the default. Except, there is no default if the wildcard token +** is a possible look-ahead. +*/ +void CompressTables(struct lemon *lemp) +{ + struct state *stp; + struct action *ap, *ap2; + struct rule *rp, *rp2, *rbest; + int nbest, n; + int i; + int usesWildcard; + + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + nbest = 0; + rbest = 0; + usesWildcard = 0; + + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type==SHIFT && ap->sp==lemp->wildcard ){ + usesWildcard = 1; + } + if( ap->type!=REDUCE ) continue; + rp = ap->x.rp; + if( rp->lhsStart ) continue; + if( rp==rbest ) continue; + n = 1; + for(ap2=ap->next; ap2; ap2=ap2->next){ + if( ap2->type!=REDUCE ) continue; + rp2 = ap2->x.rp; + if( rp2==rbest ) continue; + if( rp2==rp ) n++; + } + if( n>nbest ){ + nbest = n; + rbest = rp; + } + } + + /* Do not make a default if the number of rules to default + ** is not at least 1 or if the wildcard token is a possible + ** lookahead. + */ + if( nbest<1 || usesWildcard ) continue; + + + /* Combine matching REDUCE actions into a single default */ + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type==REDUCE && ap->x.rp==rbest ) break; + } + assert( ap ); + ap->sp = Symbol_new("{default}"); + for(ap=ap->next; ap; ap=ap->next){ + if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED; + } + stp->ap = Action_sort(stp->ap); + } +} + + +/* +** Compare two states for sorting purposes. The smaller state is the +** one with the most non-terminal actions. If they have the same number +** of non-terminal actions, then the smaller is the one with the most +** token actions. +*/ +static int stateResortCompare(const void *a, const void *b){ + const struct state *pA = *(const struct state**)a; + const struct state *pB = *(const struct state**)b; + int n; + + n = pB->nNtAct - pA->nNtAct; + if( n==0 ){ + n = pB->nTknAct - pA->nTknAct; + if( n==0 ){ + n = pB->statenum - pA->statenum; + } + } + assert( n!=0 ); + return n; +} + + +/* +** Renumber and resort states so that states with fewer choices +** occur at the end. Except, keep state 0 as the first state. +*/ +void ResortStates(struct lemon *lemp) +{ + int i; + struct state *stp; + struct action *ap; + + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + stp->nTknAct = stp->nNtAct = 0; + stp->iDflt = lemp->nstate + lemp->nrule; + stp->iTknOfst = NO_OFFSET; + stp->iNtOfst = NO_OFFSET; + for(ap=stp->ap; ap; ap=ap->next){ + if( compute_action(lemp,ap)>=0 ){ + if( ap->sp->indexnterminal ){ + stp->nTknAct++; + }else if( ap->sp->indexnsymbol ){ + stp->nNtAct++; + }else{ + stp->iDflt = compute_action(lemp, ap); + } + } + } + } + qsort(&lemp->sorted[1], lemp->nstate-1, sizeof(lemp->sorted[0]), + stateResortCompare); + for(i=0; instate; i++){ + lemp->sorted[i]->statenum = i; + } +} + + +/***************** From the file "set.c" ************************************/ +/* +** Set manipulation routines for the LEMON parser generator. +*/ + +static int size = 0; + +/* Set the set size */ +void SetSize(int n) +{ + size = n+1; +} + +/* Allocate a new set */ +char *SetNew(){ + char *s; + s = (char*)calloc( size, 1); + if( s==0 ){ + extern void memory_error(); + memory_error(); + } + return s; +} + +/* Deallocate a set */ +void SetFree(char *s) +{ + free(s); +} + +/* Add a new element to the set. Return TRUE if the element was added +** and FALSE if it was already there. */ +int SetAdd(char *s, int e) +{ + int rv; + assert( e>=0 && esize = 1024; + x1a->count = 0; + x1a->tbl = (x1node*)calloc(1024, sizeof(x1node) + sizeof(x1node*)); + if( x1a->tbl==0 ){ + free(x1a); + x1a = 0; + }else{ + int i; + x1a->ht = (x1node**)&(x1a->tbl[1024]); + for(i=0; i<1024; i++) x1a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Strsafe_insert(const char *data) +{ + x1node *np; + unsigned h; + unsigned ph; + + if( x1a==0 ) return 0; + ph = strhash(data); + h = ph & (x1a->size-1); + np = x1a->ht[h]; + while( np ){ + if( strcmp(np->data,data)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x1a->count>=x1a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x1 array; + array.size = size = x1a->size*2; + array.count = x1a->count; + array.tbl = (x1node*)calloc(size, sizeof(x1node) + sizeof(x1node*)); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x1node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x1node *oldnp, *newnp; + oldnp = &(x1a->tbl[i]); + h = strhash(oldnp->data) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x1a->tbl); + *x1a = array; + } + /* Insert the new data */ + h = ph & (x1a->size-1); + np = &(x1a->tbl[x1a->count++]); + np->data = data; + if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next); + np->next = x1a->ht[h]; + x1a->ht[h] = np; + np->from = &(x1a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +const char *Strsafe_find(const char *key) +{ + unsigned h; + x1node *np; + + if( x1a==0 ) return 0; + h = strhash(key) & (x1a->size-1); + np = x1a->ht[h]; + while( np ){ + if( strcmp(np->data,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return a pointer to the (terminal or nonterminal) symbol "x". +** Create a new symbol if this is the first time "x" has been seen. +*/ +struct symbol *Symbol_new(const char *x) +{ + struct symbol *sp; + + sp = Symbol_find(x); + if( sp==0 ){ + sp = (struct symbol *)calloc(1, sizeof(struct symbol) ); + MemoryCheck(sp); + sp->name = Strsafe(x); + sp->type = isupper(*x) ? TERMINAL : NONTERMINAL; + sp->rule = 0; + sp->fallback = 0; + sp->prec = -1; + sp->assoc = UNK; + sp->firstset = 0; + sp->lambda = LEMON_FALSE; + sp->destructor = 0; + sp->destLineno = 0; + sp->datatype = 0; + sp->useCnt = 0; + Symbol_insert(sp,sp->name); + } + sp->useCnt++; + return sp; +} + +/* Compare two symbols for sorting purposes. Return negative, +** zero, or positive if a is less then, equal to, or greater +** than b. +** +** Symbols that begin with upper case letters (terminals or tokens) +** must sort before symbols that begin with lower case letters +** (non-terminals). And MULTITERMINAL symbols (created using the +** %token_class directive) must sort at the very end. Other than +** that, the order does not matter. +** +** We find experimentally that leaving the symbols in their original +** order (the order they appeared in the grammar file) gives the +** smallest parser tables in SQLite. +*/ +int Symbolcmpp(const void *_a, const void *_b) +{ + const struct symbol *a = *(const struct symbol **) _a; + const struct symbol *b = *(const struct symbol **) _b; + int i1 = a->type==MULTITERMINAL ? 3 : a->name[0]>'Z' ? 2 : 1; + int i2 = b->type==MULTITERMINAL ? 3 : b->name[0]>'Z' ? 2 : 1; + return i1==i2 ? a->index - b->index : i1 - i2; +} + +/* There is one instance of the following structure for each +** associative array of type "x2". +*/ +struct s_x2 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x2node *tbl; /* The data stored here */ + struct s_x2node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x2". +*/ +typedef struct s_x2node { + struct symbol *data; /* The data */ + const char *key; /* The key */ + struct s_x2node *next; /* Next entry with the same hash */ + struct s_x2node **from; /* Previous link */ +} x2node; + +/* There is only one instance of the array, which is the following */ +static struct s_x2 *x2a; + +/* Allocate a new associative array */ +void Symbol_init(){ + if( x2a ) return; + x2a = (struct s_x2*)malloc( sizeof(struct s_x2) ); + if( x2a ){ + x2a->size = 128; + x2a->count = 0; + x2a->tbl = (x2node*)calloc(128, sizeof(x2node) + sizeof(x2node*)); + if( x2a->tbl==0 ){ + free(x2a); + x2a = 0; + }else{ + int i; + x2a->ht = (x2node**)&(x2a->tbl[128]); + for(i=0; i<128; i++) x2a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Symbol_insert(struct symbol *data, const char *key) +{ + x2node *np; + unsigned h; + unsigned ph; + + if( x2a==0 ) return 0; + ph = strhash(key); + h = ph & (x2a->size-1); + np = x2a->ht[h]; + while( np ){ + if( strcmp(np->key,key)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x2a->count>=x2a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x2 array; + array.size = size = x2a->size*2; + array.count = x2a->count; + array.tbl = (x2node*)calloc(size, sizeof(x2node) + sizeof(x2node*)); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x2node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x2node *oldnp, *newnp; + oldnp = &(x2a->tbl[i]); + h = strhash(oldnp->key) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->key = oldnp->key; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x2a->tbl); + *x2a = array; + } + /* Insert the new data */ + h = ph & (x2a->size-1); + np = &(x2a->tbl[x2a->count++]); + np->key = key; + np->data = data; + if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next); + np->next = x2a->ht[h]; + x2a->ht[h] = np; + np->from = &(x2a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct symbol *Symbol_find(const char *key) +{ + unsigned h; + x2node *np; + + if( x2a==0 ) return 0; + h = strhash(key) & (x2a->size-1); + np = x2a->ht[h]; + while( np ){ + if( strcmp(np->key,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return the n-th data. Return NULL if n is out of range. */ +struct symbol *Symbol_Nth(int n) +{ + struct symbol *data; + if( x2a && n>0 && n<=x2a->count ){ + data = x2a->tbl[n-1].data; + }else{ + data = 0; + } + return data; +} + +/* Return the size of the array */ +int Symbol_count() +{ + return x2a ? x2a->count : 0; +} + +/* Return an array of pointers to all data in the table. +** The array is obtained from malloc. Return NULL if memory allocation +** problems, or if the array is empty. */ +struct symbol **Symbol_arrayof() +{ + struct symbol **array; + int i,size; + if( x2a==0 ) return 0; + size = x2a->count; + array = (struct symbol **)calloc(size, sizeof(struct symbol *)); + if( array ){ + for(i=0; itbl[i].data; + } + return array; +} + +/* Compare two configurations */ +int Configcmp(const char *_a,const char *_b) +{ + const struct config *a = (struct config *) _a; + const struct config *b = (struct config *) _b; + int x; + x = a->rp->index - b->rp->index; + if( x==0 ) x = a->dot - b->dot; + return x; +} + +/* Compare two states */ +PRIVATE int statecmp(struct config *a, struct config *b) +{ + int rc; + for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){ + rc = a->rp->index - b->rp->index; + if( rc==0 ) rc = a->dot - b->dot; + } + if( rc==0 ){ + if( a ) rc = 1; + if( b ) rc = -1; + } + return rc; +} + +/* Hash a state */ +PRIVATE unsigned statehash(struct config *a) +{ + unsigned h=0; + while( a ){ + h = h*571 + a->rp->index*37 + a->dot; + a = a->bp; + } + return h; +} + +/* Allocate a new state structure */ +struct state *State_new() +{ + struct state *newstate; + newstate = (struct state *)calloc(1, sizeof(struct state) ); + MemoryCheck(newstate); + return newstate; +} + +/* There is one instance of the following structure for each +** associative array of type "x3". +*/ +struct s_x3 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x3node *tbl; /* The data stored here */ + struct s_x3node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x3". +*/ +typedef struct s_x3node { + struct state *data; /* The data */ + struct config *key; /* The key */ + struct s_x3node *next; /* Next entry with the same hash */ + struct s_x3node **from; /* Previous link */ +} x3node; + +/* There is only one instance of the array, which is the following */ +static struct s_x3 *x3a; + +/* Allocate a new associative array */ +void State_init(){ + if( x3a ) return; + x3a = (struct s_x3*)malloc( sizeof(struct s_x3) ); + if( x3a ){ + x3a->size = 128; + x3a->count = 0; + x3a->tbl = (x3node*)calloc(128, sizeof(x3node) + sizeof(x3node*)); + if( x3a->tbl==0 ){ + free(x3a); + x3a = 0; + }else{ + int i; + x3a->ht = (x3node**)&(x3a->tbl[128]); + for(i=0; i<128; i++) x3a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int State_insert(struct state *data, struct config *key) +{ + x3node *np; + unsigned h; + unsigned ph; + + if( x3a==0 ) return 0; + ph = statehash(key); + h = ph & (x3a->size-1); + np = x3a->ht[h]; + while( np ){ + if( statecmp(np->key,key)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x3a->count>=x3a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x3 array; + array.size = size = x3a->size*2; + array.count = x3a->count; + array.tbl = (x3node*)calloc(size, sizeof(x3node) + sizeof(x3node*)); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x3node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x3node *oldnp, *newnp; + oldnp = &(x3a->tbl[i]); + h = statehash(oldnp->key) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->key = oldnp->key; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x3a->tbl); + *x3a = array; + } + /* Insert the new data */ + h = ph & (x3a->size-1); + np = &(x3a->tbl[x3a->count++]); + np->key = key; + np->data = data; + if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next); + np->next = x3a->ht[h]; + x3a->ht[h] = np; + np->from = &(x3a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct state *State_find(struct config *key) +{ + unsigned h; + x3node *np; + + if( x3a==0 ) return 0; + h = statehash(key) & (x3a->size-1); + np = x3a->ht[h]; + while( np ){ + if( statecmp(np->key,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return an array of pointers to all data in the table. +** The array is obtained from malloc. Return NULL if memory allocation +** problems, or if the array is empty. */ +struct state **State_arrayof() +{ + struct state **array; + int i,size; + if( x3a==0 ) return 0; + size = x3a->count; + array = (struct state **)calloc(size, sizeof(struct state *)); + if( array ){ + for(i=0; itbl[i].data; + } + return array; +} + +/* Hash a configuration */ +PRIVATE unsigned confighash(struct config *a) +{ + unsigned h=0; + h = h*571 + a->rp->index*37 + a->dot; + return h; +} + +/* There is one instance of the following structure for each +** associative array of type "x4". +*/ +struct s_x4 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x4node *tbl; /* The data stored here */ + struct s_x4node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x4". +*/ +typedef struct s_x4node { + struct config *data; /* The data */ + struct s_x4node *next; /* Next entry with the same hash */ + struct s_x4node **from; /* Previous link */ +} x4node; + +/* There is only one instance of the array, which is the following */ +static struct s_x4 *x4a; + +/* Allocate a new associative array */ +void Configtable_init(){ + if( x4a ) return; + x4a = (struct s_x4*)malloc( sizeof(struct s_x4) ); + if( x4a ){ + x4a->size = 64; + x4a->count = 0; + x4a->tbl = (x4node*)calloc(64, sizeof(x4node) + sizeof(x4node*)); + if( x4a->tbl==0 ){ + free(x4a); + x4a = 0; + }else{ + int i; + x4a->ht = (x4node**)&(x4a->tbl[64]); + for(i=0; i<64; i++) x4a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Configtable_insert(struct config *data) +{ + x4node *np; + unsigned h; + unsigned ph; + + if( x4a==0 ) return 0; + ph = confighash(data); + h = ph & (x4a->size-1); + np = x4a->ht[h]; + while( np ){ + if( Configcmp((const char *) np->data,(const char *) data)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x4a->count>=x4a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x4 array; + array.size = size = x4a->size*2; + array.count = x4a->count; + array.tbl = (x4node*)calloc(size, sizeof(x4node) + sizeof(x4node*)); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x4node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x4node *oldnp, *newnp; + oldnp = &(x4a->tbl[i]); + h = confighash(oldnp->data) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x4a->tbl); + *x4a = array; + } + /* Insert the new data */ + h = ph & (x4a->size-1); + np = &(x4a->tbl[x4a->count++]); + np->data = data; + if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next); + np->next = x4a->ht[h]; + x4a->ht[h] = np; + np->from = &(x4a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct config *Configtable_find(struct config *key) +{ + int h; + x4node *np; + + if( x4a==0 ) return 0; + h = confighash(key) & (x4a->size-1); + np = x4a->ht[h]; + while( np ){ + if( Configcmp((const char *) np->data,(const char *) key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Remove all data from the table. Pass each data to the function "f" +** as it is removed. ("f" may be null to avoid this step.) */ +void Configtable_clear(int(*f)(struct config *)) +{ + int i; + if( x4a==0 || x4a->count==0 ) return; + if( f ) for(i=0; icount; i++) (*f)(x4a->tbl[i].data); + for(i=0; isize; i++) x4a->ht[i] = 0; + x4a->count = 0; + return; +} diff --git a/modules/luci-lua-runtime/src/contrib/lempar.c b/modules/luci-lua-runtime/src/contrib/lempar.c new file mode 100644 index 0000000000..a4e3c07ddb --- /dev/null +++ b/modules/luci-lua-runtime/src/contrib/lempar.c @@ -0,0 +1,851 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +*/ +/* First off, code is included that follows the "include" declaration +** in the input grammar file. */ +#include +%% +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +%% +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** ParseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is ParseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** ParseARG_SDECL A static variable declaration for the %extra_argument +** ParseARG_PDECL A parameter declaration for the %extra_argument +** ParseARG_STORE Code to store %extra_argument into yypParser +** ParseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +%% +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +/* The yyzerominor constant is used to initialize instances of +** YYMINORTYPE objects to zero. */ +static const YYMINORTYPE yyzerominor = { 0 }; + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +%% + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { +%% +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyidxMax; /* Maximum value of yyidx */ +#endif + int yyerrcnt; /* Shifts left before out of the error */ + ParseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
    +**
  • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
  • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
+** +** Outputs: +** None. +*/ +void ParseTrace(FILE *TraceFILE, char *zTracePrompt); +void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { +%% +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { +%% +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. +*/ +static void yyGrowStack(yyParser *p){ + int newSize; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + if( pNew ){ + p->yystack = pNew; + p->yystksz = newSize; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows to %d entries!\n", + yyTracePrompt, p->yystksz); + } +#endif + } +} +#endif + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to Parse and ParseFree. +*/ +void *ParseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyidxMax = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yystack = NULL; + pParser->yystksz = 0; + yyGrowStack(pParser); +#endif + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + ParseARG_FETCH; + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ +%% + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor(pParser, yymajor, &yytos->minor); + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +**
    +**
  • A pointer to the parser. This should be a pointer +** obtained from ParseAlloc. +**
  • A pointer to a function used to reclaim memory obtained +** from malloc. +**
+*/ +void ParseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int ParseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyidxMax; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + if( stateno>YY_SHIFT_COUNT + || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + if( iLookAhead>0 ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + } + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + int stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( i!=YY_REDUCE_USE_DFLT ); + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && iyyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer to the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; +#ifdef YYTRACKMAXSTACKDEPTH + if( yypParser->yyidx>yypParser->yyidxMax ){ + yypParser->yyidxMax = yypParser->yyidx; + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yyidx>=YYSTACKDEPTH ){ + yyStackOverflow(yypParser, yypMinor); + return; + } +#else + if( yypParser->yyidx>=yypParser->yystksz ){ + yyGrowStack(yypParser); + if( yypParser->yyidx>=yypParser->yystksz ){ + yyStackOverflow(yypParser, yypMinor); + return; + } + } +#endif + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor = *yypMinor; +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { +%% +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + ParseARG_FETCH; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + /* Silence complaints from purify about yygotominor being uninitialized + ** in some cases when it is copied into the stack after the following + ** switch. yygotominor is uninitialized when a rule reduces that does + ** not set the value of its left-hand side nonterminal. Leaving the + ** value of the nonterminal uninitialized is utterly harmless as long + ** as the value is never used. So really the only thing this code + ** accomplishes is to quieten purify. + ** + ** 2007-01-16: The wireshark project (www.wireshark.org) reports that + ** without this code, their parser segfaults. I'm not sure what there + ** parser is doing to make this happen. This is the second bug report + ** from wireshark this week. Clearly they are stressing Lemon in ways + ** that it has not been previously stressed... (SQLite ticket #2172) + */ + /*memset(&yygotominor, 0, sizeof(yygotominor));*/ + yygotominor = yyzerominor; + + + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ +%% + }; + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); + if( yyact < YYNSTATE ){ +#ifdef NDEBUG + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if( yysize ){ + yypParser->yyidx++; + yymsp -= yysize-1; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yymsp->minor = yygotominor; + }else +#endif + { + yy_shift(yypParser,yyact,yygoto,&yygotominor); + } + }else{ + assert( yyact == YYNSTATE + YYNRULE + 1 ); + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + ParseARG_FETCH; +#define TOKEN (yyminor.yy0) +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "ParseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
    +**
  • A pointer to the parser (an opaque structure.) +**
  • The major token number. +**
  • The minor token number. +**
  • An option argument of a grammar-specified type. +**
+** +** Outputs: +** None. +*/ +void Parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + ParseTOKENTYPE yyminor /* The value for the token */ + ParseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ + int yyendofinput; /* True if we are at the end of input */ +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ +#if YYSTACKDEPTH<=0 + if( yypParser->yystksz <=0 ){ + /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/ + yyminorunion = yyzerominor; + yyStackOverflow(yypParser, &yyminorunion); + return; + } +#endif + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + } + yyminorunion.yy0 = yyminor; + yyendofinput = (yymajor==0); + ParseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); + } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyactyyerrcnt--; + yymajor = YYNOCODE; + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else{ + assert( yyact == YY_ERROR_ACTION ); +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_reduce_action( + yypParser->yystack[yypParser->yyidx].stateno, + YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor,yyminorunion); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 3; + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/modules/luci-lua-runtime/src/mkversion.sh b/modules/luci-lua-runtime/src/mkversion.sh new file mode 100755 index 0000000000..e2d02c1c74 --- /dev/null +++ b/modules/luci-lua-runtime/src/mkversion.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +cat < $1 +local pcall, dofile, _G = pcall, dofile, _G + +module "luci.version" + +if pcall(dofile, "/etc/openwrt_release") and _G.DISTRIB_DESCRIPTION then + distname = "" + distversion = _G.DISTRIB_DESCRIPTION + if _G.DISTRIB_REVISION then + distrevision = _G.DISTRIB_REVISION + if not distversion:find(distrevision,1,true) then + distversion = distversion .. " " .. distrevision + end + end +else + distname = "OpenWrt" + distversion = "Development Snapshot" +end + +luciname = "${3:-LuCI}" +luciversion = "${2:-Git}" +EOF diff --git a/modules/luci-lua-runtime/src/plural_formula.y b/modules/luci-lua-runtime/src/plural_formula.y new file mode 100644 index 0000000000..1623f8b282 --- /dev/null +++ b/modules/luci-lua-runtime/src/plural_formula.y @@ -0,0 +1,43 @@ +%name pluralParse +%token_type {int} +%extra_argument {struct parse_state *s} + +%right T_QMARK. +%left T_OR. +%left T_AND. +%left T_EQ T_NE. +%left T_LT T_LE T_GT T_GE. +%left T_ADD T_SUB. +%left T_MUL T_DIV T_MOD. +%right T_NOT. +%nonassoc T_COLON T_N T_LPAREN T_RPAREN. + +%include { +#include + +struct parse_state { + int num; + int res; +}; +} + +input ::= expr(A). { s->res = A; } + +expr(A) ::= expr(B) T_QMARK expr(C) T_COLON expr(D). { A = B ? C : D; } +expr(A) ::= expr(B) T_OR expr(C). { A = B || C; } +expr(A) ::= expr(B) T_AND expr(C). { A = B && C; } +expr(A) ::= expr(B) T_EQ expr(C). { A = B == C; } +expr(A) ::= expr(B) T_NE expr(C). { A = B != C; } +expr(A) ::= expr(B) T_LT expr(C). { A = B < C; } +expr(A) ::= expr(B) T_LE expr(C). { A = B <= C; } +expr(A) ::= expr(B) T_GT expr(C). { A = B > C; } +expr(A) ::= expr(B) T_GE expr(C). { A = B >= C; } +expr(A) ::= expr(B) T_ADD expr(C). { A = B + C; } +expr(A) ::= expr(B) T_SUB expr(C). { A = B - C; } +expr(A) ::= expr(B) T_MUL expr(C). { A = B * C; } +expr(A) ::= expr(B) T_DIV expr(C). { A = B / C; } +expr(A) ::= expr(B) T_MOD expr(C). { A = B % C; } +expr(A) ::= T_NOT expr(B). { A = !B; } +expr(A) ::= T_N. { A = s->num; } +expr(A) ::= T_NUM(B). { A = B; } +expr(A) ::= T_LPAREN expr(B) T_RPAREN. { A = B; } diff --git a/modules/luci-lua-runtime/src/template_lmo.c b/modules/luci-lua-runtime/src/template_lmo.c new file mode 100644 index 0000000000..8634bc4bf3 --- /dev/null +++ b/modules/luci-lua-runtime/src/template_lmo.c @@ -0,0 +1,637 @@ +/* + * lmo - Lua Machine Objects - Base functions + * + * Copyright (C) 2009-2010 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "template_lmo.h" +#include "plural_formula.h" + +/* + * Hash function from http://www.azillionmonkeys.com/qed/hash.html + * Copyright (C) 2004-2008 by Paul Hsieh + */ + +uint32_t sfh_hash(const char *data, int len) +{ + uint32_t hash = len, tmp; + int rem; + + if (len <= 0 || data == NULL) return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (;len > 0; len--) { + hash += sfh_get16(data); + tmp = (sfh_get16(data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2*sizeof(uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: hash += sfh_get16(data); + hash ^= hash << 16; + hash ^= (signed char)data[sizeof(uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: hash += sfh_get16(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += (signed char)*data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +uint32_t lmo_canon_hash(const char *str, int len, + const char *ctx, int ctxlen, int plural) +{ + char res[4096]; + char *ptr, *end, prev; + int off; + + if (!str) + return 0; + + ptr = res; + end = res + sizeof(res); + + if (ctx) + { + for (prev = ' ', off = 0; off < ctxlen; prev = *ctx, off++, ctx++) + { + if (ptr >= end) + return 0; + + if (isspace(*ctx)) + { + if (!isspace(prev)) + *ptr++ = ' '; + } + else + { + *ptr++ = *ctx; + } + } + + if ((ptr > res) && isspace(*(ptr-1))) + ptr--; + + if (ptr >= end) + return 0; + + *ptr++ = '\1'; + } + + for (prev = ' ', off = 0; off < len; prev = *str, off++, str++) + { + if (ptr >= end) + return 0; + + if (isspace(*str)) + { + if (!isspace(prev)) + *ptr++ = ' '; + } + else + { + *ptr++ = *str; + } + } + + if ((ptr > res) && isspace(*(ptr-1))) + ptr--; + + if (plural > -1) + { + if (plural >= 100 || ptr + 3 >= end) + return 0; + + ptr += snprintf(ptr, 3, "\2%d", plural); + } + + return sfh_hash(res, ptr - res); +} + +lmo_archive_t * lmo_open(const char *file) +{ + int in = -1; + uint32_t idx_offset = 0; + struct stat s; + + lmo_archive_t *ar = NULL; + + if (stat(file, &s) == -1) + goto err; + + if ((in = open(file, O_RDONLY)) == -1) + goto err; + + if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL) + { + memset(ar, 0, sizeof(*ar)); + + ar->fd = in; + ar->size = s.st_size; + + fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC); + + if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED) + goto err; + + idx_offset = ntohl(*((const uint32_t *) + (ar->mmap + ar->size - sizeof(uint32_t)))); + + if (idx_offset >= ar->size) + goto err; + + ar->index = (lmo_entry_t *)(ar->mmap + idx_offset); + ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t); + ar->end = ar->mmap + ar->size; + + return ar; + } + +err: + if (in > -1) + close(in); + + if (ar != NULL) + { + if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) + munmap(ar->mmap, ar->size); + + free(ar); + } + + return NULL; +} + +void lmo_close(lmo_archive_t *ar) +{ + if (ar != NULL) + { + if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED)) + munmap(ar->mmap, ar->size); + + close(ar->fd); + free(ar); + + ar = NULL; + } +} + + +lmo_catalog_t *_lmo_catalogs = NULL; +lmo_catalog_t *_lmo_active_catalog = NULL; + +int lmo_load_catalog(const char *lang, const char *dir) +{ + DIR *dh = NULL; + char pattern[16]; + char path[PATH_MAX]; + struct dirent *de = NULL; + + lmo_archive_t *ar = NULL; + lmo_catalog_t *cat = NULL; + + if (!lmo_change_catalog(lang)) + return 0; + + if (!dir || !(dh = opendir(dir))) + goto err; + + if (!(cat = malloc(sizeof(*cat)))) + goto err; + + memset(cat, 0, sizeof(*cat)); + + snprintf(cat->lang, sizeof(cat->lang), "%s", lang); + snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang); + + while ((de = readdir(dh)) != NULL) + { + if (!fnmatch(pattern, de->d_name, 0)) + { + snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); + ar = lmo_open(path); + + if (ar) + { + ar->next = cat->archives; + cat->archives = ar; + } + } + } + + closedir(dh); + + cat->next = _lmo_catalogs; + _lmo_catalogs = cat; + + if (!_lmo_active_catalog) + _lmo_active_catalog = cat; + + return cat->archives ? 0 : -1; + +err: + if (dh) closedir(dh); + if (cat) free(cat); + + return -1; +} + +int lmo_change_catalog(const char *lang) +{ + lmo_catalog_t *cat; + + for (cat = _lmo_catalogs; cat; cat = cat->next) + { + if (!strncmp(cat->lang, lang, sizeof(cat->lang))) + { + _lmo_active_catalog = cat; + return 0; + } + } + + return -1; +} + +static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash) +{ + unsigned int m, l, r; + uint32_t k; + + l = 0; + r = ar->length - 1; + + while (1) + { + m = l + ((r - l) / 2); + + if (r < l) + break; + + k = ntohl(ar->index[m].key_id); + + if (k == hash) + return &ar->index[m]; + + if (k > hash) + { + if (!m) + break; + + r = m - 1; + } + else + { + l = m + 1; + } + } + + return NULL; +} + +void *pluralParseAlloc(void *(*)(size_t)); +void pluralParse(void *, int, int, void *); +void pluralParseFree(void *, void (*)(void *)); + +static int lmo_eval_plural(const char *expr, int len, int val) +{ + struct { int num; int res; } s = { .num = val, .res = -1 }; + const char *p = NULL; + void *pParser = NULL; + int t, n; + char c; + + while (len > 7) { + if (*expr == 'p') { + if (!strncmp(expr, "plural=", 7)) { + p = expr + 7; + len -= 7; + break; + } + } + + expr++; + len--; + } + + if (!p) + goto out; + + pParser = pluralParseAlloc(malloc); + + if (!pParser) + goto out; + + while (len-- > 0) { + c = *p++; + t = -1; + n = 0; + + switch (c) { + case ' ': + case '\t': + continue; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + t = T_NUM; + n = c - '0'; + + while (*p >= '0' && *p <= '9') { + n *= 10; + n += *p - '0'; + p++; + } + + break; + + case '=': + if (*p == '=') { + t = T_EQ; + p++; + } + + break; + + case '!': + if (*p == '=') { + t = T_NE; + p++; + } + else { + t = T_NOT; + } + + break; + + case '&': + if (*p == '&') { + t = T_AND; + p++; + } + + break; + + case '|': + if (*p == '|') { + t = T_OR; + p++; + } + + break; + + case '<': + if (*p == '=') { + t = T_LE; + p++; + } + else { + t = T_LT; + } + + break; + + case '>': + if (*p == '=') { + t = T_GE; + p++; + } + else { + t = T_GT; + } + + break; + + case '*': + t = T_MUL; + break; + + case '/': + t = T_DIV; + break; + + case '%': + t = T_MOD; + break; + + case '+': + t = T_ADD; + break; + + case '-': + t = T_SUB; + break; + + case 'n': + t = T_N; + break; + + case '?': + t = T_QMARK; + break; + + case ':': + t = T_COLON; + break; + + case '(': + t = T_LPAREN; + break; + + case ')': + t = T_RPAREN; + break; + + case ';': + case '\n': + case '\0': + t = 0; + break; + } + + /* syntax error */ + if (t < 0) + goto out; + + pluralParse(pParser, t, n, &s); + + /* eof */ + if (t == 0) + break; + } + + pluralParse(pParser, 0, 0, &s); + +out: + pluralParseFree(pParser, free); + + return s.res; +} + +int lmo_translate(const char *key, int keylen, char **out, int *outlen) +{ + return lmo_translate_ctxt(key, keylen, NULL, 0, out, outlen); +} + +int lmo_translate_ctxt(const char *key, int keylen, + const char *ctx, int ctxlen, + char **out, int *outlen) +{ + uint32_t hash; + lmo_entry_t *e; + lmo_archive_t *ar; + + if (!key || !_lmo_active_catalog) + return -2; + + hash = lmo_canon_hash(key, keylen, ctx, ctxlen, -1); + + if (hash > 0) + { + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) + { + if ((e = lmo_find_entry(ar, hash)) != NULL) + { + *out = ar->mmap + ntohl(e->offset); + *outlen = ntohl(e->length); + return 0; + } + } + } + + return -1; +} + +int lmo_translate_plural(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + char **out, int *outlen) +{ + return lmo_translate_plural_ctxt(n, skey, skeylen, pkey, pkeylen, + NULL, 0, out, outlen); +} + +int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + const char *ctx, int ctxlen, + char **out, int *outlen) +{ + int pid = -1; + uint32_t hash; + lmo_entry_t *e; + lmo_archive_t *ar; + const char *plural_formula; + + if (!skey || !pkey || !_lmo_active_catalog) + return -2; + + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) { + e = lmo_find_entry(ar, 0); + + if (e != NULL) { + pid = lmo_eval_plural(ar->mmap + ntohl(e->offset), ntohl(e->length), n); + break; + } + } + + if (pid == -1) + pid = (n != 1); + + hash = lmo_canon_hash(skey, skeylen, ctx, ctxlen, pid); + + if (hash == 0) + return -1; + + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) + { + if ((e = lmo_find_entry(ar, hash)) != NULL) + { + *out = ar->mmap + ntohl(e->offset); + *outlen = ntohl(e->length); + return 0; + } + } + + if (n != 1) + { + *out = (char *)pkey; + *outlen = pkeylen; + } + else + { + *out = (char *)skey; + *outlen = skeylen; + } + + return 0; +} + +void lmo_iterate(lmo_iterate_cb_t cb, void *priv) +{ + unsigned int i; + lmo_entry_t *e; + lmo_archive_t *ar; + + if (!_lmo_active_catalog) + return; + + for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) + for (i = 0, e = &ar->index[0]; i < ar->length; e = &ar->index[++i]) + cb(ntohl(e->key_id), ar->mmap + ntohl(e->offset), ntohl(e->length), priv); +} + +void lmo_close_catalog(const char *lang) +{ + lmo_archive_t *ar, *next; + lmo_catalog_t *cat, *prev; + + for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next) + { + if (!strncmp(cat->lang, lang, sizeof(cat->lang))) + { + if (prev) + prev->next = cat->next; + else + _lmo_catalogs = cat->next; + + for (ar = cat->archives; ar; ar = next) + { + next = ar->next; + lmo_close(ar); + } + + free(cat); + break; + } + } +} diff --git a/modules/luci-lua-runtime/src/template_lmo.h b/modules/luci-lua-runtime/src/template_lmo.h new file mode 100644 index 0000000000..d6cba7bf49 --- /dev/null +++ b/modules/luci-lua-runtime/src/template_lmo.h @@ -0,0 +1,104 @@ +/* + * lmo - Lua Machine Objects - General header + * + * Copyright (C) 2009-2012 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TEMPLATE_LMO_H_ +#define _TEMPLATE_LMO_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (defined(__GNUC__) && defined(__i386__)) +#define sfh_get16(d) (*((const uint16_t *) (d))) +#else +#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + + +struct lmo_entry { + uint32_t key_id; + uint32_t val_id; + uint32_t offset; + uint32_t length; +} __attribute__((packed)); + +typedef struct lmo_entry lmo_entry_t; + + +struct lmo_archive { + int fd; + int length; + uint32_t size; + lmo_entry_t *index; + char *mmap; + char *end; + struct lmo_archive *next; +}; + +typedef struct lmo_archive lmo_archive_t; + + +struct lmo_catalog { + char lang[6]; + struct lmo_archive *archives; + struct lmo_catalog *next; +}; + +typedef struct lmo_catalog lmo_catalog_t; + +typedef void (*lmo_iterate_cb_t)(uint32_t, const char *, int, void *); + +uint32_t sfh_hash(const char *data, int len); +uint32_t lmo_canon_hash(const char *data, int len, + const char *ctx, int ctxlen, int plural); + +lmo_archive_t * lmo_open(const char *file); +void lmo_close(lmo_archive_t *ar); + + +extern lmo_catalog_t *_lmo_catalogs; +extern lmo_catalog_t *_lmo_active_catalog; + +int lmo_load_catalog(const char *lang, const char *dir); +int lmo_change_catalog(const char *lang); +int lmo_translate(const char *key, int keylen, char **out, int *outlen); +int lmo_translate_ctxt(const char *key, int keylen, + const char *ctx, int ctxlen, char **out, int *outlen); +int lmo_translate_plural(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + char **out, int *outlen); +int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen, + const char *pkey, int pkeylen, + const char *ctx, int ctxlen, + char **out, int *outlen); +void lmo_iterate(lmo_iterate_cb_t cb, void *priv); +void lmo_close_catalog(const char *lang); + +#endif diff --git a/modules/luci-lua-runtime/src/template_lualib.c b/modules/luci-lua-runtime/src/template_lualib.c new file mode 100644 index 0000000000..4efd9f1de6 --- /dev/null +++ b/modules/luci-lua-runtime/src/template_lualib.c @@ -0,0 +1,224 @@ +/* + * LuCI Template - Lua binding + * + * Copyright (C) 2009 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "template_lualib.h" + +static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname) +{ + int lua_status, rv; + + if (!parser) + { + lua_pushnil(L); + lua_pushinteger(L, errno); + lua_pushstring(L, strerror(errno)); + return 3; + } + + lua_status = lua_load(L, template_reader, parser, chunkname); + + if (lua_status == 0) + rv = 1; + else + rv = template_error(L, parser); + + template_close(parser); + + return rv; +} + +int template_L_parse(lua_State *L) +{ + const char *file = luaL_checkstring(L, 1); + struct template_parser *parser = template_open(file); + + return template_L_do_parse(L, parser, file); +} + +int template_L_parse_string(lua_State *L) +{ + size_t len; + const char *str = luaL_checklstring(L, 1, &len); + struct template_parser *parser = template_string(str, len); + + return template_L_do_parse(L, parser, "[string]"); +} + +int template_L_utf8(lua_State *L) +{ + size_t len = 0; + const char *str = luaL_checklstring(L, 1, &len); + char *res = utf8(str, len); + + if (res != NULL) + { + lua_pushstring(L, res); + free(res); + + return 1; + } + + return 0; +} + +int template_L_pcdata(lua_State *L) +{ + size_t len = 0; + const char *str = luaL_checklstring(L, 1, &len); + char *res = pcdata(str, len); + + if (res != NULL) + { + lua_pushstring(L, res); + free(res); + + return 1; + } + + return 0; +} + +int template_L_striptags(lua_State *L) +{ + size_t len = 0; + const char *str = luaL_checklstring(L, 1, &len); + char *res = striptags(str, len); + + if (res != NULL) + { + lua_pushstring(L, res); + free(res); + + return 1; + } + + return 0; +} + +static int template_L_load_catalog(lua_State *L) { + const char *lang = luaL_optstring(L, 1, "en"); + const char *dir = luaL_optstring(L, 2, NULL); + lua_pushboolean(L, !lmo_load_catalog(lang, dir)); + return 1; +} + +static int template_L_close_catalog(lua_State *L) { + const char *lang = luaL_optstring(L, 1, "en"); + lmo_close_catalog(lang); + return 0; +} + +static int template_L_change_catalog(lua_State *L) { + const char *lang = luaL_optstring(L, 1, "en"); + lua_pushboolean(L, !lmo_change_catalog(lang)); + return 1; +} + +static void template_L_get_translations_cb(uint32_t key, const char *val, int len, void *priv) { + lua_State *L = priv; + char hex[9]; + + luaL_checktype(L, 1, LUA_TFUNCTION); + snprintf(hex, sizeof(hex), "%08x", key); + + lua_pushvalue(L, 1); + lua_pushstring(L, hex); + lua_pushlstring(L, val, len); + lua_call(L, 2, 0); +} + +static int template_L_get_translations(lua_State *L) { + lmo_iterate(template_L_get_translations_cb, L); + return 0; +} + +static int template_L_translate(lua_State *L) { + size_t len, ctxlen = 0; + char *tr; + int trlen; + const char *key = luaL_checklstring(L, 1, &len); + const char *ctx = luaL_optlstring(L, 2, NULL, &ctxlen); + + switch (lmo_translate_ctxt(key, len, ctx, ctxlen, &tr, &trlen)) + { + case 0: + lua_pushlstring(L, tr, trlen); + return 1; + + case -1: + return 0; + } + + lua_pushnil(L); + lua_pushstring(L, "no catalog loaded"); + return 2; +} + +static int template_L_ntranslate(lua_State *L) { + size_t slen, plen, ctxlen = 0; + char *tr; + int trlen; + int n = luaL_checkinteger(L, 1); + const char *skey = luaL_checklstring(L, 2, &slen); + const char *pkey = luaL_checklstring(L, 3, &plen); + const char *ctx = luaL_optlstring(L, 4, NULL, &ctxlen); + + switch (lmo_translate_plural_ctxt(n, skey, slen, pkey, plen, ctx, ctxlen, &tr, &trlen)) + { + case 0: + lua_pushlstring(L, tr, trlen); + return 1; + + case -1: + return 0; + } + + lua_pushnil(L); + lua_pushstring(L, "no catalog loaded"); + return 2; +} + +static int template_L_hash(lua_State *L) { + size_t len; + const char *key = luaL_checklstring(L, 1, &len); + lua_pushinteger(L, sfh_hash(key, len)); + return 1; +} + + +/* module table */ +static const luaL_reg R[] = { + { "parse", template_L_parse }, + { "parse_string", template_L_parse_string }, + { "utf8", template_L_utf8 }, + { "pcdata", template_L_pcdata }, + { "striptags", template_L_striptags }, + { "load_catalog", template_L_load_catalog }, + { "close_catalog", template_L_close_catalog }, + { "change_catalog", template_L_change_catalog }, + { "get_translations", template_L_get_translations }, + { "translate", template_L_translate }, + { "ntranslate", template_L_ntranslate }, + { "hash", template_L_hash }, + { NULL, NULL } +}; + +LUALIB_API int luaopen_luci_template_parser(lua_State *L) { + luaL_register(L, TEMPLATE_LUALIB_META, R); + return 1; +} diff --git a/modules/luci-lua-runtime/src/template_lualib.h b/modules/luci-lua-runtime/src/template_lualib.h new file mode 100644 index 0000000000..ff7746d158 --- /dev/null +++ b/modules/luci-lua-runtime/src/template_lualib.h @@ -0,0 +1,30 @@ +/* + * LuCI Template - Lua library header + * + * Copyright (C) 2009 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TEMPLATE_LUALIB_H_ +#define _TEMPLATE_LUALIB_H_ + +#include "template_parser.h" +#include "template_utils.h" +#include "template_lmo.h" + +#define TEMPLATE_LUALIB_META "template.parser" + +LUALIB_API int luaopen_luci_template_parser(lua_State *L); + +#endif diff --git a/modules/luci-lua-runtime/src/template_parser.c b/modules/luci-lua-runtime/src/template_parser.c new file mode 100644 index 0000000000..0ef08c63d2 --- /dev/null +++ b/modules/luci-lua-runtime/src/template_parser.c @@ -0,0 +1,419 @@ +/* + * LuCI Template - Parser implementation + * + * Copyright (C) 2009-2012 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "template_parser.h" +#include "template_utils.h" +#include "template_lmo.h" + + +/* leading and trailing code for different types */ +const char *gen_code[9][2] = { + { NULL, NULL }, + { "write(\"", "\")" }, + { NULL, NULL }, + { "write(tostring(", " or \"\"))" }, + { "include(\"", "\")" }, + { "write(\"", "\")" }, + { "write(\"", "\")" }, + { NULL, " " }, + { NULL, NULL }, +}; + +/* Simple strstr() like function that takes len arguments for both haystack and needle. */ +static char *strfind(char *haystack, int hslen, const char *needle, int ndlen) +{ + int match = 0; + int i, j; + + for( i = 0; i < hslen; i++ ) + { + if( haystack[i] == needle[0] ) + { + match = ((ndlen == 1) || ((i + ndlen) <= hslen)); + + for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ ) + { + if( haystack[i+j] != needle[j] ) + { + match = 0; + break; + } + } + + if( match ) + return &haystack[i]; + } + } + + return NULL; +} + +struct template_parser * template_open(const char *file) +{ + struct stat s; + struct template_parser *parser; + + if (!(parser = malloc(sizeof(*parser)))) + goto err; + + memset(parser, 0, sizeof(*parser)); + parser->fd = -1; + parser->file = file; + + if (stat(file, &s)) + goto err; + + if ((parser->fd = open(file, O_RDONLY)) < 0) + goto err; + + parser->size = s.st_size; + parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE, + parser->fd, 0); + + if (parser->data != MAP_FAILED) + { + parser->off = parser->data; + parser->cur_chunk.type = T_TYPE_INIT; + parser->cur_chunk.s = parser->data; + parser->cur_chunk.e = parser->data; + + return parser; + } + +err: + template_close(parser); + return NULL; +} + +struct template_parser * template_string(const char *str, uint32_t len) +{ + struct template_parser *parser; + + if (!str) { + errno = EINVAL; + goto err; + } + + if (!(parser = malloc(sizeof(*parser)))) + goto err; + + memset(parser, 0, sizeof(*parser)); + parser->fd = -1; + + parser->size = len; + parser->data = (char*)str; + + parser->off = parser->data; + parser->cur_chunk.type = T_TYPE_INIT; + parser->cur_chunk.s = parser->data; + parser->cur_chunk.e = parser->data; + + return parser; + +err: + template_close(parser); + return NULL; +} + +void template_close(struct template_parser *parser) +{ + if (!parser) + return; + + if (parser->gc != NULL) + free(parser->gc); + + /* if file is not set, we were parsing a string */ + if (parser->file) { + if ((parser->data != NULL) && (parser->data != MAP_FAILED)) + munmap(parser->data, parser->size); + + if (parser->fd >= 0) + close(parser->fd); + } + + free(parser); +} + +void template_text(struct template_parser *parser, const char *e) +{ + const char *s = parser->off; + + if (s < (parser->data + parser->size)) + { + if (parser->strip_after) + { + while ((s <= e) && isspace(*s)) + s++; + } + + parser->cur_chunk.type = T_TYPE_TEXT; + } + else + { + parser->cur_chunk.type = T_TYPE_EOF; + } + + parser->cur_chunk.line = parser->line; + parser->cur_chunk.s = s; + parser->cur_chunk.e = e; +} + +void template_code(struct template_parser *parser, const char *e) +{ + const char *s = parser->off; + + parser->strip_before = 0; + parser->strip_after = 0; + + if (*s == '-') + { + parser->strip_before = 1; + for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++); + } + + if (*(e-1) == '-') + { + parser->strip_after = 1; + for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--); + } + + switch (*s) + { + /* comment */ + case '#': + s++; + parser->cur_chunk.type = T_TYPE_COMMENT; + break; + + /* include */ + case '+': + s++; + parser->cur_chunk.type = T_TYPE_INCLUDE; + break; + + /* translate */ + case ':': + s++; + parser->cur_chunk.type = T_TYPE_I18N; + break; + + /* translate raw */ + case '_': + s++; + parser->cur_chunk.type = T_TYPE_I18N_RAW; + break; + + /* expr */ + case '=': + s++; + parser->cur_chunk.type = T_TYPE_EXPR; + break; + + /* code */ + default: + parser->cur_chunk.type = T_TYPE_CODE; + break; + } + + parser->cur_chunk.line = parser->line; + parser->cur_chunk.s = s; + parser->cur_chunk.e = e; +} + +static const char * +template_format_chunk(struct template_parser *parser, size_t *sz) +{ + const char *s, *p; + const char *head, *tail; + struct template_chunk *c = &parser->prv_chunk; + struct template_buffer *buf; + + *sz = 0; + s = parser->gc = NULL; + + if (parser->strip_before && c->type == T_TYPE_TEXT) + { + while ((c->e > c->s) && isspace(*(c->e - 1))) + c->e--; + } + + /* empty chunk */ + if (c->s == c->e) + { + if (c->type == T_TYPE_EOF) + { + *sz = 0; + s = NULL; + } + else + { + *sz = 1; + s = " "; + } + } + + /* format chunk */ + else if ((buf = buf_init(c->e - c->s)) != NULL) + { + if ((head = gen_code[c->type][0]) != NULL) + buf_append(buf, head, strlen(head)); + + switch (c->type) + { + case T_TYPE_TEXT: + luastr_escape(buf, c->s, c->e - c->s, 0); + break; + + case T_TYPE_EXPR: + buf_append(buf, c->s, c->e - c->s); + for (p = c->s; p < c->e; p++) + parser->line += (*p == '\n'); + break; + + case T_TYPE_INCLUDE: + luastr_escape(buf, c->s, c->e - c->s, 0); + break; + + case T_TYPE_I18N: + luastr_translate(buf, c->s, c->e - c->s, 1); + break; + + case T_TYPE_I18N_RAW: + luastr_translate(buf, c->s, c->e - c->s, 0); + break; + + case T_TYPE_CODE: + buf_append(buf, c->s, c->e - c->s); + for (p = c->s; p < c->e; p++) + parser->line += (*p == '\n'); + break; + } + + if ((tail = gen_code[c->type][1]) != NULL) + buf_append(buf, tail, strlen(tail)); + + *sz = buf_length(buf); + s = parser->gc = buf_destroy(buf); + + if (!*sz) + { + *sz = 1; + s = " "; + } + } + + return s; +} + +const char *template_reader(lua_State *L, void *ud, size_t *sz) +{ + struct template_parser *parser = ud; + int rem = parser->size - (parser->off - parser->data); + char *tag; + + parser->prv_chunk = parser->cur_chunk; + + /* free previous string */ + if (parser->gc) + { + free(parser->gc); + parser->gc = NULL; + } + + /* before tag */ + if (!parser->in_expr) + { + if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL) + { + template_text(parser, tag); + parser->off = tag + 2; + parser->in_expr = 1; + } + else + { + template_text(parser, parser->data + parser->size); + parser->off = parser->data + parser->size; + } + } + + /* inside tag */ + else + { + if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL) + { + template_code(parser, tag); + parser->off = tag + 2; + parser->in_expr = 0; + } + else + { + /* unexpected EOF */ + template_code(parser, parser->data + parser->size); + + *sz = 1; + return "\033"; + } + } + + return template_format_chunk(parser, sz); +} + +int template_error(lua_State *L, struct template_parser *parser) +{ + const char *err = luaL_checkstring(L, -1); + const char *off = parser->prv_chunk.s; + const char *ptr; + char msg[1024]; + int line = 0; + int chunkline = 0; + + if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL) + { + chunkline = atoi(ptr + 2) - parser->prv_chunk.line; + + while (*ptr) + { + if (*ptr++ == ' ') + { + err = ptr; + break; + } + } + } + + if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL) + { + off = parser->data + parser->size; + err = "'%>' expected before end of file"; + chunkline = 0; + } + + for (ptr = parser->data; ptr < off; ptr++) + if (*ptr == '\n') + line++; + + snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s", + parser->file ? parser->file : "[string]", line + chunkline, err ? err : "(unknown error)"); + + lua_pushnil(L); + lua_pushinteger(L, line + chunkline); + lua_pushstring(L, msg); + + return 3; +} diff --git a/modules/luci-lua-runtime/src/template_parser.h b/modules/luci-lua-runtime/src/template_parser.h new file mode 100644 index 0000000000..2415e87079 --- /dev/null +++ b/modules/luci-lua-runtime/src/template_parser.h @@ -0,0 +1,80 @@ +/* + * LuCI Template - Parser header + * + * Copyright (C) 2009 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TEMPLATE_PARSER_H_ +#define _TEMPLATE_PARSER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +/* code types */ +#define T_TYPE_INIT 0 +#define T_TYPE_TEXT 1 +#define T_TYPE_COMMENT 2 +#define T_TYPE_EXPR 3 +#define T_TYPE_INCLUDE 4 +#define T_TYPE_I18N 5 +#define T_TYPE_I18N_RAW 6 +#define T_TYPE_CODE 7 +#define T_TYPE_EOF 8 + + +struct template_chunk { + const char *s; + const char *e; + int type; + int line; +}; + +/* parser state */ +struct template_parser { + int fd; + uint32_t size; + char *data; + char *off; + char *gc; + int line; + int in_expr; + int strip_before; + int strip_after; + struct template_chunk prv_chunk; + struct template_chunk cur_chunk; + const char *file; +}; + +struct template_parser * template_open(const char *file); +struct template_parser * template_string(const char *str, uint32_t len); +void template_close(struct template_parser *parser); + +const char *template_reader(lua_State *L, void *ud, size_t *sz); +int template_error(lua_State *L, struct template_parser *parser); + +#endif diff --git a/modules/luci-lua-runtime/src/template_utils.c b/modules/luci-lua-runtime/src/template_utils.c new file mode 100644 index 0000000000..8580405e32 --- /dev/null +++ b/modules/luci-lua-runtime/src/template_utils.c @@ -0,0 +1,500 @@ +/* + * LuCI Template - Utility functions + * + * Copyright (C) 2010 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "template_utils.h" +#include "template_lmo.h" + +/* initialize a buffer object */ +struct template_buffer * buf_init(int size) +{ + struct template_buffer *buf; + + if (size <= 0) + size = 1024; + + buf = (struct template_buffer *)malloc(sizeof(struct template_buffer)); + + if (buf != NULL) + { + buf->fill = 0; + buf->size = size; + buf->data = malloc(buf->size); + + if (buf->data != NULL) + { + buf->dptr = buf->data; + buf->data[0] = 0; + + return buf; + } + + free(buf); + } + + return NULL; +} + +/* grow buffer */ +int buf_grow(struct template_buffer *buf, int size) +{ + unsigned int off = (buf->dptr - buf->data); + char *data; + + if (size <= 0) + size = 1024; + + data = realloc(buf->data, buf->size + size); + + if (data != NULL) + { + buf->data = data; + buf->dptr = data + off; + buf->size += size; + + return buf->size; + } + + return 0; +} + +/* put one char into buffer object */ +int buf_putchar(struct template_buffer *buf, char c) +{ + if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) ) + return 0; + + *(buf->dptr++) = c; + *(buf->dptr) = 0; + + buf->fill++; + return 1; +} + +/* append data to buffer */ +int buf_append(struct template_buffer *buf, const char *s, int len) +{ + if ((buf->fill + len + 1) >= buf->size) + { + if (!buf_grow(buf, len + 1)) + return 0; + } + + memcpy(buf->dptr, s, len); + buf->fill += len; + buf->dptr += len; + + *(buf->dptr) = 0; + + return len; +} + +/* read buffer length */ +int buf_length(struct template_buffer *buf) +{ + return buf->fill; +} + +/* destroy buffer object and return pointer to data */ +char * buf_destroy(struct template_buffer *buf) +{ + char *data = buf->data; + + free(buf); + return data; +} + + +/* calculate the number of expected continuation chars */ +static inline int mb_num_chars(unsigned char c) +{ + if ((c & 0xE0) == 0xC0) + return 2; + else if ((c & 0xF0) == 0xE0) + return 3; + else if ((c & 0xF8) == 0xF0) + return 4; + else if ((c & 0xFC) == 0xF8) + return 5; + else if ((c & 0xFE) == 0xFC) + return 6; + + return 1; +} + +/* test whether the given byte is a valid continuation char */ +static inline int mb_is_cont(unsigned char c) +{ + return ((c >= 0x80) && (c <= 0xBF)); +} + +/* test whether the byte sequence at the given pointer with the given + * length is the shortest possible representation of the code point */ +static inline int mb_is_shortest(unsigned char *s, int n) +{ + switch (n) + { + case 2: + /* 1100000x (10xxxxxx) */ + return !(((*s >> 1) == 0x60) && + ((*(s+1) >> 6) == 0x02)); + + case 3: + /* 11100000 100xxxxx (10xxxxxx) */ + return !((*s == 0xE0) && + ((*(s+1) >> 5) == 0x04) && + ((*(s+2) >> 6) == 0x02)); + + case 4: + /* 11110000 1000xxxx (10xxxxxx 10xxxxxx) */ + return !((*s == 0xF0) && + ((*(s+1) >> 4) == 0x08) && + ((*(s+2) >> 6) == 0x02) && + ((*(s+3) >> 6) == 0x02)); + + case 5: + /* 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx) */ + return !((*s == 0xF8) && + ((*(s+1) >> 3) == 0x10) && + ((*(s+2) >> 6) == 0x02) && + ((*(s+3) >> 6) == 0x02) && + ((*(s+4) >> 6) == 0x02)); + + case 6: + /* 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) */ + return !((*s == 0xF8) && + ((*(s+1) >> 2) == 0x20) && + ((*(s+2) >> 6) == 0x02) && + ((*(s+3) >> 6) == 0x02) && + ((*(s+4) >> 6) == 0x02) && + ((*(s+5) >> 6) == 0x02)); + } + + return 1; +} + +/* test whether the byte sequence at the given pointer with the given + * length is an UTF-16 surrogate */ +static inline int mb_is_surrogate(unsigned char *s, int n) +{ + return ((n == 3) && (*s == 0xED) && (*(s+1) >= 0xA0) && (*(s+1) <= 0xBF)); +} + +/* test whether the byte sequence at the given pointer with the given + * length is an illegal UTF-8 code point */ +static inline int mb_is_illegal(unsigned char *s, int n) +{ + return ((n == 3) && (*s == 0xEF) && (*(s+1) == 0xBF) && + (*(s+2) >= 0xBE) && (*(s+2) <= 0xBF)); +} + + +/* scan given source string, validate UTF-8 sequence and store result + * in given buffer object */ +static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf) +{ + unsigned char *ptr = *s; + unsigned int o = 0, v, n; + + /* ascii byte without null */ + if ((*(ptr+0) >= 0x01) && (*(ptr+0) <= 0x7F)) + { + if (!buf_putchar(buf, *ptr++)) + return 0; + + o = 1; + } + + /* multi byte sequence */ + else if ((n = mb_num_chars(*ptr)) > 1) + { + /* count valid chars */ + for (v = 1; (v <= n) && ((o+v) < l) && mb_is_cont(*(ptr+v)); v++); + + switch (n) + { + case 6: + case 5: + /* five and six byte sequences are always invalid */ + if (!buf_putchar(buf, '?')) + return 0; + + break; + + default: + /* if the number of valid continuation bytes matches the + * expected number and if the sequence is legal, copy + * the bytes to the destination buffer */ + if ((v == n) && mb_is_shortest(ptr, n) && + !mb_is_surrogate(ptr, n) && !mb_is_illegal(ptr, n)) + { + /* copy sequence */ + if (!buf_append(buf, (char *)ptr, n)) + return 0; + } + + /* the found sequence is illegal, skip it */ + else + { + /* invalid sequence */ + if (!buf_putchar(buf, '?')) + return 0; + } + + break; + } + + /* advance beyond the last found valid continuation char */ + o = v; + ptr += v; + } + + /* invalid byte (0x00) */ + else + { + if (!buf_putchar(buf, '?')) /* or 0xEF, 0xBF, 0xBD */ + return 0; + + o = 1; + ptr++; + } + + *s = ptr; + return o; +} + +/* sanitize given string and replace all invalid UTF-8 sequences with "?" */ +char * utf8(const char *s, unsigned int l) +{ + struct template_buffer *buf = buf_init(l); + unsigned char *ptr = (unsigned char *)s; + unsigned int v, o; + + if (!buf) + return NULL; + + for (o = 0; o < l; o++) + { + /* ascii char */ + if ((*ptr >= 0x01) && (*ptr <= 0x7F)) + { + if (!buf_putchar(buf, (char)*ptr++)) + break; + } + + /* invalid byte or multi byte sequence */ + else + { + if (!(v = _validate_utf8(&ptr, l - o, buf))) + break; + + o += (v - 1); + } + } + + return buf_destroy(buf); +} + +/* Sanitize given string and strip all invalid XML bytes + * Validate UTF-8 sequences + * Escape XML control chars */ +char * pcdata(const char *s, unsigned int l) +{ + struct template_buffer *buf = buf_init(l); + unsigned char *ptr = (unsigned char *)s; + unsigned int o, v; + char esq[8]; + int esl; + + if (!buf) + return NULL; + + for (o = 0; o < l; o++) + { + /* Invalid XML bytes */ + if (((*ptr >= 0x00) && (*ptr <= 0x08)) || + ((*ptr >= 0x0B) && (*ptr <= 0x0C)) || + ((*ptr >= 0x0E) && (*ptr <= 0x1F)) || + (*ptr == 0x7F)) + { + ptr++; + } + + /* Escapes */ + else if ((*ptr == 0x26) || + (*ptr == 0x27) || + (*ptr == 0x22) || + (*ptr == 0x3C) || + (*ptr == 0x3E)) + { + esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); + + if (!buf_append(buf, esq, esl)) + break; + + ptr++; + } + + /* ascii char */ + else if (*ptr <= 0x7F) + { + buf_putchar(buf, (char)*ptr++); + } + + /* multi byte sequence */ + else + { + if (!(v = _validate_utf8(&ptr, l - o, buf))) + break; + + o += (v - 1); + } + } + + return buf_destroy(buf); +} + +char * striptags(const char *s, unsigned int l) +{ + struct template_buffer *buf = buf_init(l); + unsigned char *ptr = (unsigned char *)s; + unsigned char *end = ptr + l; + unsigned char *tag; + unsigned char prev; + char esq[8]; + int esl; + + for (prev = ' '; ptr < end; ptr++) + { + if ((*ptr == '<') && ((ptr + 2) < end) && + ((*(ptr + 1) == '/') || isalpha(*(ptr + 1)))) + { + for (tag = ptr; tag < end; tag++) + { + if (*tag == '>') + { + if (!isspace(prev)) + buf_putchar(buf, ' '); + + ptr = tag; + prev = ' '; + break; + } + } + } + else if (isspace(*ptr)) + { + if (!isspace(prev)) + buf_putchar(buf, *ptr); + + prev = *ptr; + } + else + { + switch(*ptr) + { + case '"': + case '\'': + case '<': + case '>': + case '&': + esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); + buf_append(buf, esq, esl); + break; + + default: + buf_putchar(buf, *ptr); + break; + } + + prev = *ptr; + } + } + + return buf_destroy(buf); +} + +void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, + int escape_xml) +{ + int esl; + char esq[8]; + char *ptr; + + for (ptr = (char *)s; ptr < (s + l); ptr++) + { + switch (*ptr) + { + case '\\': + buf_append(out, "\\\\", 2); + break; + + case '"': + if (escape_xml) + buf_append(out, """, 5); + else + buf_append(out, "\\\"", 2); + break; + + case '\n': + buf_append(out, "\\n", 2); + break; + + case '\'': + case '&': + case '<': + case '>': + if (escape_xml) + { + esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); + buf_append(out, esq, esl); + break; + } + + default: + buf_putchar(out, *ptr); + } + } +} + +void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, + int escape_xml) +{ + int trlen, idlen = l, ctxtlen = 0, esc = 0; + const char *p, *msgid = s, *msgctxt = NULL; + char *tr; + + for (p = s; p < s + l; p++) { + if (esc) { + esc = 0; + } + else if (*p == '\\') { + esc = 1; + } + else if (*p == '|') { + idlen = p - s; + msgctxt = p + 1; + ctxtlen = s + l - msgctxt; + break; + } + } + + if (!lmo_translate_ctxt(msgid, idlen, msgctxt, ctxtlen, &tr, &trlen)) + luastr_escape(out, tr, trlen, escape_xml); + else + luastr_escape(out, s, l, escape_xml); +} diff --git a/modules/luci-lua-runtime/src/template_utils.h b/modules/luci-lua-runtime/src/template_utils.h new file mode 100644 index 0000000000..32a79f93bc --- /dev/null +++ b/modules/luci-lua-runtime/src/template_utils.h @@ -0,0 +1,49 @@ +/* + * LuCI Template - Utility header + * + * Copyright (C) 2010-2012 Jo-Philipp Wich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TEMPLATE_UTILS_H_ +#define _TEMPLATE_UTILS_H_ + +#include +#include +#include + + +/* buffer object */ +struct template_buffer { + char *data; + char *dptr; + unsigned int size; + unsigned int fill; +}; + +struct template_buffer * buf_init(int size); +int buf_grow(struct template_buffer *buf, int size); +int buf_putchar(struct template_buffer *buf, char c); +int buf_append(struct template_buffer *buf, const char *s, int len); +int buf_length(struct template_buffer *buf); +char * buf_destroy(struct template_buffer *buf); + +char * utf8(const char *s, unsigned int l); +char * pcdata(const char *s, unsigned int l); +char * striptags(const char *s, unsigned int l); + +void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, int escape_xml); +void luastr_translate(struct template_buffer *out, const char *s, unsigned int l, int escape_xml); + +#endif